Browse Source

feat: 添加image export插件

liaojiaxing 3 months ago
parent
commit
ca23c1de7f

+ 0 - 1
apps/designer/package.json

@@ -9,7 +9,6 @@
     "start": "npm run dev"
   },
   "dependencies": {
-    "@repo/ui": "workspace:*",
     "@repo/x6-plugin-selection": "workspace:*",
     "@unocss/cli": "^0.62.3",
     "umi": "^4.3.18",

+ 1 - 2
apps/er-designer/package.json

@@ -9,8 +9,7 @@
     "start": "npm run dev"
   },
   "dependencies": {
-    "@repo/ui": "workspace:*",
-    "@repo/x6-plugin-selection": "workspace:*",
+    "@repo/x6-plugin-export": "workspace:*",
     "@unocss/cli": "^0.62.3",
     "umi": "^4.3.18",
     "unocss": "^0.62.3"

+ 1 - 1
apps/er-designer/src/models/erModel.tsx

@@ -4,7 +4,7 @@ import { Transform } from "@antv/x6-plugin-transform";
 import { Scroller } from "@antv/x6-plugin-scroller";
 import { Snapline } from "@antv/x6-plugin-snapline";
 import { Keyboard } from "@antv/x6-plugin-keyboard";
-import { Export } from "@antv/x6-plugin-export";
+import { Export } from "@repo/x6-plugin-export";
 import { Selection } from "@antv/x6-plugin-selection";
 import { SaveDataModel, UploadFile } from "@/api";
 import { useFullscreen, useSessionStorageState } from "ahooks";

+ 21 - 0
packages/x6-plugin-export/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021-2023 Alipay.inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 5 - 0
packages/x6-plugin-export/README.md

@@ -0,0 +1,5 @@
+# `x6-plugin-export`
+
+> TODO: description
+
+## Usage

+ 7 - 0
packages/x6-plugin-export/package.json

@@ -0,0 +1,7 @@
+{
+  "name": "@repo/x6-plugin-export",
+  "version": "0.0.0",
+  "private": true,
+  "module": "./src/index.ts",
+  "types": "./src/index.ts"
+}

+ 82 - 0
packages/x6-plugin-export/src/api.ts

@@ -0,0 +1,82 @@
+import { Graph } from '@antv/x6'
+import { Export } from './index'
+
+declare module '@antv/x6/lib/graph/graph' {
+  interface Graph {
+    toSVG: (
+      callback: Export.ToSVGCallback,
+      options?: Export.ToSVGOptions,
+    ) => void
+    toPNG: (
+      callback: Export.ToSVGCallback,
+      options?: Export.ToImageOptions,
+    ) => void
+    toJPEG: (
+      callback: Export.ToSVGCallback,
+      options?: Export.ToImageOptions,
+    ) => void
+    exportPNG: (fileName?: string, options?: Export.ToImageOptions) => void
+    exportJPEG: (fileName?: string, options?: Export.ToImageOptions) => void
+    exportSVG: (fileName?: string, options?: Export.ToSVGOptions) => void
+  }
+}
+
+Graph.prototype.toSVG = function (
+  callback: Export.ToSVGCallback,
+  options?: Export.ToSVGOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.toSVG(callback, options)
+  }
+}
+
+Graph.prototype.toPNG = function (
+  callback: Export.ToSVGCallback,
+  options?: Export.ToImageOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.toPNG(callback, options)
+  }
+}
+
+Graph.prototype.toJPEG = function (
+  callback: Export.ToSVGCallback,
+  options?: Export.ToImageOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.toJPEG(callback, options)
+  }
+}
+
+Graph.prototype.exportPNG = function (
+  fileName?: string,
+  options?: Export.ToImageOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.exportPNG(fileName, options)
+  }
+}
+
+Graph.prototype.exportJPEG = function (
+  fileName?: string,
+  options?: Export.ToImageOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.exportJPEG(fileName, options)
+  }
+}
+
+Graph.prototype.exportSVG = function (
+  fileName?: string,
+  options?: Export.ToSVGOptions,
+) {
+  const instance = this.getPlugin('export') as Export
+  if (instance) {
+    instance.exportSVG(fileName, options)
+  }
+}

+ 408 - 0
packages/x6-plugin-export/src/index.ts

@@ -0,0 +1,408 @@
+import {
+  DataUri,
+  NumberExt,
+  FunctionExt,
+  Vector,
+  Rectangle,
+  Size,
+  KeyValue,
+  Basecoat,
+  Dom,
+  Graph,
+} from '@antv/x6'
+import './api'
+
+export class Export extends Basecoat<Export.EventArgs> implements Graph.Plugin {
+  public name = 'export'
+  private graph: Graph
+
+  constructor() {
+    super()
+  }
+
+  get view() {
+    return this.graph.view
+  }
+
+  init(graph: Graph) {
+    this.graph = graph
+  }
+
+  exportPNG(fileName = 'chart', options: Export.ToImageOptions = {}) {
+    this.toPNG((dataUri) => {
+      DataUri.downloadDataUri(dataUri, fileName)
+    }, options)
+  }
+
+  exportJPEG(fileName = 'chart', options: Export.ToImageOptions = {}) {
+    this.toJPEG((dataUri) => {
+      DataUri.downloadDataUri(dataUri, fileName)
+    }, options)
+  }
+
+  exportSVG(fileName = 'chart', options: Export.ToSVGOptions = {}) {
+    this.toSVG((svg: string) => {
+      DataUri.downloadDataUri(DataUri.svgToDataUrl(svg), fileName)
+    }, options)
+  }
+
+  toSVG(callback: Export.ToSVGCallback, options: Export.ToSVGOptions = {}) {
+    this.notify('before:export', options)
+
+    // to keep pace with it's doc description witch, the default value should be true.
+    // without Object.hasOwn method cause by ts config target.
+    // without instance.hasOwnProperty method cause by ts rule.
+    // the condition will be false if these properties have been set undefined in the target,
+    // but will be true if these properties are not in the target, cause the doc.
+    !Object.prototype.hasOwnProperty.call(options, 'copyStyle') &&
+      (options.copyStyles = true)
+    !Object.prototype.hasOwnProperty.call(options, 'serializeImages') &&
+      (options.serializeImages = true)
+
+    const rawSVG = this.view.svg
+    const vSVG = Vector.create(rawSVG).clone()
+    let clonedSVG = vSVG.node as SVGSVGElement
+    const vStage = vSVG.findOne(
+      `.${this.view.prefixClassName('graph-svg-stage')}`,
+    )!
+
+    const viewBox =
+      options.viewBox || this.graph.graphToLocal(this.graph.getContentBBox())
+    const dimension = options.preserveDimensions
+    if (dimension) {
+      const size = typeof dimension === 'boolean' ? viewBox : dimension
+      vSVG.attr({
+        width: size.width,
+        height: size.height,
+      })
+    }
+
+    vSVG
+      .removeAttribute('style')
+      .attr(
+        'viewBox',
+        [viewBox.x, viewBox.y, viewBox.width, viewBox.height].join(' '),
+      )
+
+    vStage.removeAttribute('transform')
+
+    // Stores all the CSS declarations from external stylesheets to the
+    // `style` attribute of the SVG document nodes.
+
+    // This is achieved in three steps.
+    // -----------------------------------
+
+    // 1. Disabling all the stylesheets in the page and therefore collecting
+    //    only default style values. This, together with the step 2, makes it
+    //    possible to discard default CSS property values and store only those
+    //    that differ.
+    //
+    // 2. Enabling back all the stylesheets in the page and collecting styles
+    //    that differ from the default values.
+    //
+    // 3. Applying the difference between default values and the ones set by
+    //    custom stylesheets onto the `style` attribute of each of the nodes
+    //    in SVG.
+
+    if (options.copyStyles) {
+      const document = rawSVG.ownerDocument!
+      const raws = Array.from(rawSVG.querySelectorAll('*'))
+      const clones = Array.from(clonedSVG.querySelectorAll('*'))
+
+      const styleSheetCount = document.styleSheets.length
+      const styleSheetsCopy = []
+      for (let k = styleSheetCount - 1; k >= 0; k -= 1) {
+        // There is a bug (bugSS) in Chrome 14 and Safari. When you set
+        // `stylesheet.disable = true` it will also remove it from
+        // `document.styleSheets`. So we need to store all stylesheets before
+        // we disable them. Later on we put them back to `document.styleSheets`
+        // if needed.
+
+        // See the bug `https://code.google.com/p/chromium/issues/detail?id=88310`.
+        styleSheetsCopy[k] = document.styleSheets[k]
+        document.styleSheets[k].disabled = true
+      }
+
+      const defaultComputedStyles: KeyValue<KeyValue<string>> = {}
+      raws.forEach((elem, index) => {
+        const computedStyle = window.getComputedStyle(elem, null)
+        // We're making a deep copy of the `computedStyle` so that it's not affected
+        // by that next step when all the stylesheets are re-enabled again.
+        const defaultComputedStyle: KeyValue<string> = {}
+        Object.keys(computedStyle).forEach((property) => {
+          defaultComputedStyle[property] =
+            computedStyle.getPropertyValue(property)
+        })
+
+        defaultComputedStyles[index] = defaultComputedStyle
+      })
+
+      // Copy all stylesheets back
+      if (styleSheetCount !== document.styleSheets.length) {
+        styleSheetsCopy.forEach((copy, index) => {
+          document.styleSheets[index] = copy
+        })
+      }
+
+      for (let i = 0; i < styleSheetCount; i += 1) {
+        document.styleSheets[i].disabled = false
+      }
+
+      const customStyles: KeyValue<KeyValue<string>> = {}
+      raws.forEach((elem, index) => {
+        const computedStyle = window.getComputedStyle(elem, null)
+        const defaultComputedStyle = defaultComputedStyles[index]
+        const customStyle: KeyValue<string> = {}
+
+        Object.keys(computedStyle).forEach((property) => {
+          if (
+            !NumberExt.isNumber(property) &&
+            computedStyle.getPropertyValue(property) !==
+              defaultComputedStyle[property]
+          ) {
+            customStyle[property] = computedStyle.getPropertyValue(property)
+          }
+        })
+
+        customStyles[index] = customStyle
+      })
+
+      clones.forEach((elem, index) => {
+        Dom.css(elem, customStyles[index])
+      })
+    }
+
+    const stylesheet = options.stylesheet
+    if (typeof stylesheet === 'string') {
+      const cDATASection = rawSVG
+        .ownerDocument!.implementation.createDocument(null, 'xml', null)
+        .createCDATASection(stylesheet)
+
+      vSVG.prepend(
+        Vector.create(
+          'style',
+          {
+            type: 'text/css',
+          },
+          [cDATASection as any],
+        ),
+      )
+    }
+
+    const format = () => {
+      const beforeSerialize = options.beforeSerialize
+      if (typeof beforeSerialize === 'function') {
+        const ret = FunctionExt.call(beforeSerialize, this.graph, clonedSVG)
+        if (ret instanceof SVGSVGElement) {
+          clonedSVG = ret
+        }
+      }
+
+      const dataUri = new XMLSerializer()
+        .serializeToString(clonedSVG)
+        .replace(/&nbsp;/g, '\u00a0')
+
+      this.notify('after:export', options)
+      callback(dataUri)
+    }
+
+    if (options.serializeImages) {
+      const deferrals = vSVG.find('image').map((vImage) => {
+        return new Promise<void>((resolve) => {
+          const url = vImage.attr('xlink:href') || vImage.attr('href')
+          DataUri.imageToDataUri(url, (err, dataUri) => {
+            if (!err && dataUri) {
+              vImage.attr('xlink:href', dataUri)
+            }
+            resolve()
+          })
+        })
+      })
+
+      Promise.all(deferrals).then(format)
+    } else {
+      format()
+    }
+  }
+
+  toDataURL(callback: Export.ToSVGCallback, options: Export.ToDataURLOptions) {
+    let viewBox = options.viewBox || this.graph.getContentBBox()
+
+    const padding = NumberExt.normalizeSides(options.padding)
+    if (options.width && options.height) {
+      if (padding.left + padding.right >= options.width) {
+        padding.left = padding.right = 0
+      }
+      if (padding.top + padding.bottom >= options.height) {
+        padding.top = padding.bottom = 0
+      }
+    }
+
+    const expanding = new Rectangle(
+      -padding.left,
+      -padding.top,
+      padding.left + padding.right,
+      padding.top + padding.bottom,
+    )
+
+    if (options.width && options.height) {
+      const width = viewBox.width + padding.left + padding.right
+      const height = viewBox.height + padding.top + padding.bottom
+      expanding.scale(width / options.width, height / options.height)
+    }
+
+    viewBox = Rectangle.create(viewBox).moveAndExpand(expanding)
+
+    const rawSize =
+      typeof options.width === 'number' && typeof options.height === 'number'
+        ? { width: options.width, height: options.height }
+        : viewBox
+
+    let scale = options.ratio ? parseFloat(options.ratio) : 1
+    if (!Number.isFinite(scale) || scale === 0) {
+      scale = 1
+    }
+
+    const size = {
+      width: Math.max(Math.round(rawSize.width * scale), 1),
+      height: Math.max(Math.round(rawSize.height * scale), 1),
+    }
+
+    {
+      const imgDataCanvas = document.createElement('canvas')
+      const context2D = imgDataCanvas.getContext('2d')!
+      imgDataCanvas.width = size.width
+      imgDataCanvas.height = size.height
+      const x = size.width - 1
+      const y = size.height - 1
+      context2D.fillStyle = 'rgb(1,1,1)'
+      context2D.fillRect(x, y, 1, 1)
+      const data = context2D.getImageData(x, y, 1, 1).data
+      if (data[0] !== 1 || data[1] !== 1 || data[2] !== 1) {
+        throw new Error('size exceeded')
+      }
+    }
+
+    const img = new Image()
+    img.onload = () => {
+      const canvas = document.createElement('canvas')
+      canvas.width = size.width
+      canvas.height = size.height
+
+      const context = canvas.getContext('2d')!
+      context.fillStyle = options.backgroundColor || 'white'
+      context.fillRect(0, 0, size.width, size.height)
+
+      try {
+        context.drawImage(img, 0, 0, size.width, size.height)
+        const dataUri = canvas.toDataURL(options.type, options.quality)
+        callback(dataUri)
+      } catch (error) {
+        // pass
+      }
+    }
+
+    this.toSVG(
+      (dataUri) => {
+        img.src = `data:image/svg+xml,${encodeURIComponent(dataUri)}`
+      },
+      {
+        ...options,
+        viewBox,
+        serializeImages: true,
+        preserveDimensions: {
+          ...size,
+        },
+      },
+    )
+  }
+
+  toPNG(callback: Export.ToSVGCallback, options: Export.ToImageOptions = {}) {
+    this.toDataURL(callback, {
+      ...options,
+      type: 'image/png',
+    })
+  }
+
+  toJPEG(callback: Export.ToSVGCallback, options: Export.ToImageOptions = {}) {
+    this.toDataURL(callback, {
+      ...options,
+      type: 'image/jpeg',
+    })
+  }
+
+  protected notify<K extends keyof Export.EventArgs>(
+    name: K,
+    args: Export.EventArgs[K],
+  ) {
+    this.trigger(name, args)
+    this.graph.trigger(name, args)
+  }
+
+  @Basecoat.dispose()
+  dispose(): void {
+    this.off()
+  }
+}
+
+export namespace Export {
+  export interface EventArgs {
+    'before:export': Export.ToSVGOptions
+    'after:export': Export.ToSVGOptions
+  }
+
+  export type ToSVGCallback = (dataUri: string) => any
+
+  export interface ToSVGOptions {
+    /**
+     * By default, the resulting SVG has set width and height to `100%`.
+     * If you'd like to have the dimensions to be set to the actual content
+     * width and height, set `preserveDimensions` to `true`. An object with
+     * `width` and `height` properties can be also used here if you need to
+     * define the export size explicitely.
+     */
+    preserveDimensions?: boolean | Size
+
+    viewBox?: Rectangle.RectangleLike
+
+    /**
+     * When set to `true` all the styles from external stylesheets are copied
+     * to the resulting SVG export. Note this requires a lot of computations
+     * and it might significantly affect the export time.
+     */
+    copyStyles?: boolean
+
+    stylesheet?: string
+
+    /**
+     * Converts all contained images into Data URI format.
+     */
+    serializeImages?: boolean
+
+    /**
+     * A function called before the XML serialization. It may be used to
+     * modify the exported SVG before it is converted to a string. The
+     * function can also return a new SVGDocument.
+     */
+    beforeSerialize?: (this: Graph, svg: SVGSVGElement) => any
+  }
+
+  export interface ToImageOptions extends ToSVGOptions {
+    /**
+     * The width of the image in pixels.
+     */
+    width?: number
+    /**
+     * The height of the image in pixels.
+     */
+    height?: number
+    ratio?: string
+    backgroundColor?: string
+    padding?: NumberExt.SideOptions
+    quality?: number
+  }
+
+  export interface ToDataURLOptions extends ToImageOptions {
+    type: 'image/png' | 'image/jpeg'
+  }
+}

+ 20 - 0
packages/x6-plugin-export/tsconfig.json

@@ -0,0 +1,20 @@
+{
+  "exclude": ["node_modules", "**/es", "**/lib"],
+  "compilerOptions": {
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "sourceMap": true,
+    "declaration": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "noUnusedLocals": true,
+    "strictNullChecks": true,
+    "resolveJsonModule": true,
+    "experimentalDecorators": true,
+    "jsx": "react",
+    "target": "es6",
+    "lib": ["DOM", "ES2020"]
+  }
+}

+ 9 - 178
pnpm-lock.yaml

@@ -145,7 +145,7 @@ importers:
         version: 2.0.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)
       umi:
         specifier: ^4.3.18
-        version: 4.3.19(@babel/core@7.25.2)(eslint@8.57.0)(prettier@3.3.3)(react-dom@18.3.1)(react@18.3.1)(stylelint@14.16.1)(typescript@5.5.4)(webpack@5.94.0)
+        version: 4.3.19(@babel/core@7.25.2)(@types/react@18.3.5)(eslint@8.57.0)(prettier@3.3.3)(react-dom@18.3.1)(react@18.3.1)(stylelint@14.16.1)(typescript@5.5.4)(webpack@5.94.0)
       unocss:
         specifier: ^0.62.3
         version: 0.62.3(postcss@8.4.45)(vite@5.4.3)
@@ -168,9 +168,6 @@ importers:
 
   apps/designer:
     dependencies:
-      '@repo/ui':
-        specifier: workspace:*
-        version: link:../../packages/ui
       '@repo/x6-plugin-selection':
         specifier: workspace:*
         version: link:../../packages/x6-plugin-selection
@@ -239,12 +236,9 @@ importers:
 
   apps/er-designer:
     dependencies:
-      '@repo/ui':
+      '@repo/x6-plugin-export':
         specifier: workspace:*
-        version: link:../../packages/ui
-      '@repo/x6-plugin-selection':
-        specifier: workspace:*
-        version: link:../../packages/x6-plugin-selection
+        version: link:../../packages/x6-plugin-export
       '@unocss/cli':
         specifier: ^0.62.3
         version: 0.62.3
@@ -328,6 +322,12 @@ importers:
         specifier: ^5.3.3
         version: 5.5.4
 
+  packages/x6-plugin-export:
+    devDependencies:
+      '@antv/x6':
+        specifier: ^2.x
+        version: 2.18.1
+
   packages/x6-plugin-selection: {}
 
 packages:
@@ -759,11 +759,9 @@ packages:
     dependencies:
       lodash-es: 4.17.21
       utility-types: 3.11.0
-    dev: false
 
   /@antv/x6-geometry@2.0.5:
     resolution: {integrity: sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==}
-    dev: false
 
   /@antv/x6-plugin-clipboard@2.1.6(@antv/x6@2.18.1):
     resolution: {integrity: sha512-roZPLnZx6PK8MBvee0QMo90fz/TXeF0WNe4EGin2NBq5M1I5XTWrYvA6N2XVIiWAAI67gjQeEE8TpkL7f8QdqA==}
@@ -864,7 +862,6 @@ packages:
       '@antv/x6-common': 2.0.17
       '@antv/x6-geometry': 2.0.5
       utility-types: 3.11.0
-    dev: false
 
   /@babel/code-frame@7.24.7:
     resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
@@ -2830,20 +2827,6 @@ packages:
       - react-dom
     dev: false
 
-  /@floating-ui/react-dom-interactions@0.3.1(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg==}
-    deprecated: Package renamed to @floating-ui/react
-    dependencies:
-      '@floating-ui/react-dom': 0.6.3(react-dom@18.3.1)(react@18.3.1)
-      aria-hidden: 1.2.4
-      point-in-polygon: 1.1.0
-      use-isomorphic-layout-effect: 1.1.2(react@18.3.1)
-    transitivePeerDependencies:
-      - '@types/react'
-      - react
-      - react-dom
-    dev: false
-
   /@floating-ui/react-dom@0.6.3(@types/react@18.3.5)(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==}
     peerDependencies:
@@ -2858,20 +2841,6 @@ packages:
       - '@types/react'
     dev: false
 
-  /@floating-ui/react-dom@0.6.3(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==}
-    peerDependencies:
-      react: '>=16.8.0'
-      react-dom: '>=16.8.0'
-    dependencies:
-      '@floating-ui/dom': 0.4.5
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-      use-isomorphic-layout-effect: 1.1.2(react@18.3.1)
-    transitivePeerDependencies:
-      - '@types/react'
-    dev: false
-
   /@formatjs/intl-displaynames@1.2.10:
     resolution: {integrity: sha512-GROA2RP6+7Ouu0WnHFF78O5XIU7pBfI19WM1qm93l6MFWibUk67nCfVCK3VAYJkLy8L8ZxjkYT11VIAfvSz8wg==}
     dependencies:
@@ -5121,66 +5090,6 @@ packages:
       - webpack-plugin-serve
     dev: false
 
-  /@umijs/preset-umi@4.3.19(typescript@5.5.4)(webpack@5.94.0):
-    resolution: {integrity: sha512-un/1R06c92nv9uuTRRChDOu3wjFwUU4YhTvXnlvzzj5BmqLaUF/miHapvTn0CTviDVl0UTEL/wEZ8bmDnqTj1Q==}
-    dependencies:
-      '@iconify/utils': 2.1.1
-      '@svgr/core': 6.5.1
-      '@umijs/ast': 4.3.19
-      '@umijs/babel-preset-umi': 4.3.19
-      '@umijs/bundler-esbuild': 4.3.19
-      '@umijs/bundler-mako': 0.8.8
-      '@umijs/bundler-utils': 4.3.19
-      '@umijs/bundler-vite': 4.3.19(postcss@8.4.45)
-      '@umijs/bundler-webpack': 4.3.19(typescript@5.5.4)(webpack@5.94.0)
-      '@umijs/core': 4.3.19
-      '@umijs/did-you-know': 1.0.3
-      '@umijs/es-module-parser': 0.0.7
-      '@umijs/history': 5.3.1
-      '@umijs/mfsu': 4.3.19
-      '@umijs/plugin-run': 4.3.19
-      '@umijs/renderer-react': 4.3.19(react-dom@18.3.1)(react@18.3.1)
-      '@umijs/server': 4.3.19
-      '@umijs/ui': 3.0.1
-      '@umijs/utils': 4.3.19
-      '@umijs/zod2ts': 4.3.19
-      babel-plugin-dynamic-import-node: 2.3.3
-      babel-plugin-react-compiler: 0.0.0-experimental-c23de8d-20240515
-      click-to-react-component: 1.1.0(react-dom@18.3.1)(react@18.3.1)
-      core-js: 3.34.0
-      current-script-polyfill: 1.0.0
-      enhanced-resolve: 5.9.3
-      fast-glob: 3.2.12
-      html-webpack-plugin: 5.5.0(webpack@5.94.0)
-      less-plugin-resolve: 1.0.2
-      path-to-regexp: 1.7.0
-      postcss: 8.4.45
-      postcss-prefix-selector: 1.16.0(postcss@8.4.45)
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-      react-router: 6.3.0(react@18.3.1)
-      react-router-dom: 6.3.0(react-dom@18.3.1)(react@18.3.1)
-      regenerator-runtime: 0.13.11
-    transitivePeerDependencies:
-      - '@types/node'
-      - '@types/react'
-      - '@types/webpack'
-      - lightningcss
-      - rollup
-      - sass
-      - sockjs-client
-      - stylus
-      - sugarss
-      - supports-color
-      - terser
-      - type-fest
-      - typescript
-      - webpack
-      - webpack-dev-server
-      - webpack-hot-middleware
-      - webpack-plugin-serve
-    dev: false
-
   /@umijs/react-refresh-webpack-plugin@0.5.11(react-refresh@0.14.0)(webpack@5.94.0):
     resolution: {integrity: sha512-RtFvB+/GmjRhpHcqNgnw8iWZpTlxOnmNxi8eDcecxMmxmSgeDj25LV0jr4Q6rOhv3GTIfVGBhkwz+khGT5tfmg==}
     engines: {node: '>= 10.13'}
@@ -6713,20 +6622,6 @@ packages:
       - react-dom
     dev: false
 
-  /click-to-react-component@1.1.0(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-/DjZemufS1BkxyRgZL3r7HXVVOFRWVQi5Xd4EBnjxZMwrHEh0OlUVA2N9CjXkZ0x8zMf8dL1cKnnx+xUWUg4VA==}
-    peerDependencies:
-      react: '>=16.8.0'
-    dependencies:
-      '@floating-ui/react-dom-interactions': 0.3.1(react-dom@18.3.1)(react@18.3.1)
-      htm: 3.1.1
-      react: 18.3.1
-      react-merge-refs: 1.1.0
-    transitivePeerDependencies:
-      - '@types/react'
-      - react-dom
-    dev: false
-
   /client-only@0.0.1:
     resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
 
@@ -14511,57 +14406,6 @@ packages:
       - webpack-plugin-serve
     dev: false
 
-  /umi@4.3.19(@babel/core@7.25.2)(eslint@8.57.0)(prettier@3.3.3)(react-dom@18.3.1)(react@18.3.1)(stylelint@14.16.1)(typescript@5.5.4)(webpack@5.94.0):
-    resolution: {integrity: sha512-RXIn3+NokatSuqB7jw1XXk0kQlKz8tRcIuv0gQ0CKAc/ltyseMnePEg7DgBFazvBS7s23oVIP8e4oTHHiLO+KA==}
-    engines: {node: '>=14'}
-    hasBin: true
-    dependencies:
-      '@babel/runtime': 7.23.6
-      '@umijs/bundler-utils': 4.3.19
-      '@umijs/bundler-webpack': 4.3.19(typescript@5.5.4)(webpack@5.94.0)
-      '@umijs/core': 4.3.19
-      '@umijs/lint': 4.3.19(eslint@8.57.0)(stylelint@14.16.1)(typescript@5.5.4)
-      '@umijs/preset-umi': 4.3.19(typescript@5.5.4)(webpack@5.94.0)
-      '@umijs/renderer-react': 4.3.19(react-dom@18.3.1)(react@18.3.1)
-      '@umijs/server': 4.3.19
-      '@umijs/test': 4.3.19(@babel/core@7.25.2)
-      '@umijs/utils': 4.3.19
-      prettier-plugin-organize-imports: 3.2.4(prettier@3.3.3)(typescript@5.5.4)
-      prettier-plugin-packagejson: 2.4.3(prettier@3.3.3)
-    transitivePeerDependencies:
-      - '@babel/core'
-      - '@types/node'
-      - '@types/react'
-      - '@types/webpack'
-      - '@volar/vue-language-plugin-pug'
-      - '@volar/vue-typescript'
-      - eslint
-      - jest
-      - lightningcss
-      - postcss-html
-      - postcss-jsx
-      - postcss-less
-      - postcss-markdown
-      - postcss-scss
-      - prettier
-      - react
-      - react-dom
-      - rollup
-      - sass
-      - sockjs-client
-      - stylelint
-      - stylus
-      - sugarss
-      - supports-color
-      - terser
-      - type-fest
-      - typescript
-      - webpack
-      - webpack-dev-server
-      - webpack-hot-middleware
-      - webpack-plugin-serve
-    dev: false
-
   /unbox-primitive@1.0.2:
     resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
     dependencies:
@@ -14700,18 +14544,6 @@ packages:
       react: 18.3.1
     dev: false
 
-  /use-isomorphic-layout-effect@1.1.2(react@18.3.1):
-    resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      react: 18.3.1
-    dev: false
-
   /use-sync-external-store@1.2.0(react@18.3.1):
     resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
     peerDependencies:
@@ -14761,7 +14593,6 @@ packages:
   /utility-types@3.11.0:
     resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
     engines: {node: '>= 4'}
-    dev: false
 
   /utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}