├── src ├── shim.d.ts ├── app.scss ├── asset │ └── font │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── iconfont.css │ │ └── iconfont.svg ├── main.tsx ├── type.d.ts ├── packages │ ├── style │ │ ├── _variable.scss │ │ ├── VisualEditorBlock.module.scss │ │ └── VisualEditor.module.scss │ ├── hook │ │ ├── useUpdate.ts │ │ └── useCallbackRef.ts │ ├── utils │ │ └── defer.ts │ ├── plugin │ │ ├── event.ts │ │ ├── keyboard-code.ts │ │ └── command.plugin.ts │ ├── service │ │ ├── dropdown │ │ │ ├── $$dropdown.scss │ │ │ └── $$dropdown.tsx │ │ └── dialog │ │ │ └── $$dialog.tsx │ ├── visual.config.tsx │ ├── EditorBlock.tsx │ ├── editor.utils.tsx │ ├── editor.command.tsx │ └── VisualEditor.tsx ├── App.tsx └── mock.json ├── doc ├── 右键菜单.png ├── 最终效果.png ├── 顶部操作栏.png ├── 容器区域组件基础渲染.png ├── 左侧菜单列表渲染.png ├── 画布容器中组件选中.png └── 开发文档.md ├── public └── img │ └── default-img.jpg ├── index.html ├── .yarnrc ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json ├── vite.config.ts └── yarn.lock /src/shim.d.ts: -------------------------------------------------------------------------------- 1 | // enable types for import.meta.* and file types 2 | import 'vite/env' -------------------------------------------------------------------------------- /doc/右键菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/右键菜单.png -------------------------------------------------------------------------------- /doc/最终效果.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/最终效果.png -------------------------------------------------------------------------------- /doc/顶部操作栏.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/顶部操作栏.png -------------------------------------------------------------------------------- /doc/容器区域组件基础渲染.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/容器区域组件基础渲染.png -------------------------------------------------------------------------------- /doc/左侧菜单列表渲染.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/左侧菜单列表渲染.png -------------------------------------------------------------------------------- /doc/画布容器中组件选中.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/doc/画布容器中组件选中.png -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | 2 | html, body { 3 | height: 100%; 4 | width: 100%; 5 | margin: 0; 6 | padding: 0; 7 | } -------------------------------------------------------------------------------- /public/img/default-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/public/img/default-img.jpg -------------------------------------------------------------------------------- /src/asset/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/src/asset/font/iconfont.eot -------------------------------------------------------------------------------- /src/asset/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/src/asset/font/iconfont.ttf -------------------------------------------------------------------------------- /src/asset/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/src/asset/font/iconfont.woff -------------------------------------------------------------------------------- /src/asset/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyios8859-1/react-app-editor-project/HEAD/src/asset/font/iconfont.woff2 -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import App from './App'; 3 | import 'antd/dist/antd.css'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.querySelector('#app') 8 | ) 9 | -------------------------------------------------------------------------------- /src/type.d.ts: -------------------------------------------------------------------------------- 1 | declare type React = {} 2 | 3 | declare module '*.module.scss' { 4 | const classes: { readonly [key: string]: string }; 5 | export default classes; 6 | } 7 | declare module '*.scss'; -------------------------------------------------------------------------------- /src/packages/style/_variable.scss: -------------------------------------------------------------------------------- 1 | $headSize: 60px; 2 | $menuSize: 275px; 3 | $operatorSize: 275px; 4 | 5 | $ibc: #dcdfe6; 6 | $ibl: #ebeef5; 7 | $itc: #314659; 8 | $icc: rgba(0, 0, 0, 0.45); 9 | $boxShadowColor: #f0f1f2; 10 | $primary: #409EFF; 11 | 12 | $gap: 20px; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite React Editor 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/packages/hook/useUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo, useState } from "react"; 2 | 3 | // export function useUpdate() { 4 | // const [count, setCount] = useState(0) 5 | // return useCallback(() => setCount(count + 1), [count]) 6 | // } 7 | export function useUpdate () { 8 | const [count, setCount] = useState(0); 9 | return useMemo(() => ({froceUpdate: () => setCount(count + 1)}), [count]); 10 | } 11 | -------------------------------------------------------------------------------- /src/packages/hook/useCallbackRef.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef } from "react"; 2 | 3 | /** 4 | * 需要得到一个不变的函数引用,但是这个不变的函数执行的时候,执行的是传递的最新的函数 5 | * @param callback 传递最新的函数 6 | * @returns 返回不变的函数 7 | */ 8 | export function useCallbackRef void>(callback: T): T { 9 | const refCallback = useRef(callback); 10 | refCallback.current = callback; 11 | return useCallback(((...args: any[]) => refCallback.current(...args)) as T, []); 12 | } -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | home "https://registry.yarnpkg.com" 2 | registry "https://registry.npm.taobao.org" 3 | sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" 4 | phantomjs_cdnurl "http://cnpmjs.org/downloads" 5 | electron_mirror "https://npm.taobao.org/mirrors/electron/" 6 | sqlite3_binary_host_mirror "https://foxgis.oss-cn-shanghai.aliyuncs.com/" 7 | profiler_binary_host_mirror "https://npm.taobao.org/mirrors/node-inspector/" 8 | chromedriver_cdnurl "https://cdn.npm.taobao.org/dist/chromedriver" 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # Environment Variables 27 | .env 28 | .env.build 29 | -------------------------------------------------------------------------------- /src/packages/utils/defer.ts: -------------------------------------------------------------------------------- 1 | interface Defer { 2 | (): { 3 | resolve: () => void, 4 | reject: () => void, 5 | promise: Promise 6 | }, 7 | 8 | (): { 9 | resolve: (val: T) => void, 10 | reject: () => void, 11 | promise: Promise 12 | }, 13 | } 14 | 15 | export const defer: Defer = () => { 16 | const dfd = {} as any 17 | dfd.promise = new Promise((resolve, reject) => { 18 | dfd.resolve = resolve as any 19 | dfd.reject = reject 20 | }) 21 | return dfd 22 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "types": ["node"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /src/packages/plugin/event.ts: -------------------------------------------------------------------------------- 1 | type CustomEventListener = () => void; 2 | 3 | export function createEvent() { 4 | const listeners: CustomEventListener[] = []; 5 | return { 6 | on: (cb: CustomEventListener) => { 7 | listeners.push(cb); 8 | }, 9 | off: (cb: CustomEventListener) => { 10 | const index = listeners.indexOf(cb); 11 | if (index > -1) { 12 | listeners.splice(index, 1); 13 | } 14 | }, 15 | emit: () => { 16 | listeners.forEach(c => c()); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/packages/service/dropdown/$$dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown-service { 2 | position: fixed; 3 | z-index: 999; 4 | box-shadow: 0 1px 10px 2px rgba(0, 0, 0, 0.15); 5 | background-color: #fff; 6 | padding: 12px 0; 7 | transition: opacity 150ms linear; 8 | user-select: none; 9 | } 10 | .dropdown-item { 11 | padding: 2px 12px; 12 | font-weight: 400; 13 | font-size: 14px; 14 | line-height: 22px; 15 | color: #314569; 16 | transition: background-color linear 0.2s; 17 | span { 18 | margin-left: 2px; 19 | } 20 | &:hover { 21 | background-color: #f2f2f2; 22 | cursor: pointer; 23 | } 24 | } -------------------------------------------------------------------------------- /src/packages/visual.config.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input } from "antd"; 2 | import { createVisualConfig } from "./editor.utils"; 3 | 4 | export const visualConfig = createVisualConfig(); 5 | 6 | visualConfig.registryComponent('text', { 7 | label: '文本', 8 | preview: () => 预览文本, 9 | render: () => 渲染文本 10 | }); 11 | 12 | visualConfig.registryComponent('button', { 13 | label: '按钮', 14 | preview: () => , 15 | render: () => 16 | }); 17 | 18 | visualConfig.registryComponent('input', { 19 | label: '输入框', 20 | preview: () => , 21 | render: () => 22 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React 可视化拖拽编辑器 2 | 3 | **任务清单** 4 | 5 | - [X] 去除 React.StrictMode;(主要是 React Hooks 有影响,产生其奇怪的 BUG, 比如的 useMemo的第二个参数不管是那个一个值变化都要重新执行该函数的第一个回调函数,因为返回了新的对象,没有起到优化作用,还有 useState 等多个函数) 6 | - [X] 主页面结构:左侧菜单栏可选组件列表、中间容器画布、右侧编辑组件定义的属性; 7 | - [X] 从菜单栏拖拽组件到容器; 8 | - [X] 组件(Block)在容器的选中状态; 9 | - [X] 容器内组件可移动位置; 10 | - [X] 命令队列及对应的快捷键; 11 | - [X] 容器内的组件单选、多选、全选; 12 | - [X] 操作栏按钮: 13 | - [X] 撤销、重做 **重难点**; 14 | - [X] 置顶、置底; 15 | - [X] 删除、清空; 16 | - [X] 预览、关闭编辑模式; 17 | - [X] 导入、导出; 18 | - [X] 右键菜单; 19 | - [X] 拖拽粘性贴边; 20 | - [X] 组件可以拖动调整高度和宽度(height,width); 21 | - [X] 组件可以设置预定好的属性(props); 22 | - [X] 组件绑定值(model); 23 | - [X] 设置组件标识(soltName),根据这个标识,定义某个组件的行为(函数触发)和插槽的实现(自定义视图); 24 | - [X] 完善可选组件列表: 25 | - [X] 输入框:双向绑定值,调整宽度; 26 | - [X] 按钮:类型、文字、大小尺寸、拖拽调整宽高; 27 | - [X] 图片:自定义图片地址、拖拽调整图片宽高 28 | - [X] 下拉框:预定义选项值、双向绑定字段; -------------------------------------------------------------------------------- /src/packages/style/VisualEditorBlock.module.scss: -------------------------------------------------------------------------------- 1 | @import './_variable.scss'; 2 | 3 | @mixin pos { 4 | content: ''; 5 | position: absolute; 6 | top: -2px; 7 | right: -2px; 8 | bottom: -2px; 9 | left: -2px; 10 | } 11 | 12 | .visual-editor__block { 13 | position: absolute; 14 | width: auto; 15 | height: auto; 16 | box-sizing: border-box; 17 | user-select: none; 18 | &.editor-block__mask { 19 | &::after { 20 | @include pos(); 21 | pointer-events: none; 22 | border: 2px solid transparent; 23 | background-color: transparent; 24 | z-index: 1; 25 | } 26 | } 27 | &.editor-block__active { 28 | &::before { 29 | @include pos(); 30 | border: 2px dashed $primary; 31 | z-index: 2; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import data from './mock.json'; 4 | 5 | import { VisualEditor } from "./packages/VisualEditor"; 6 | import { visualConfig } from "./packages/visual.config"; 7 | import { VisualEditorValue } from "./packages/editor.utils"; 8 | 9 | export default () => { 10 | 11 | const [editorValue, setEditorValue] = useState(() => { 12 | const val: VisualEditorValue = data; 13 | return val; 14 | }); 15 | 16 | return ( 17 |
18 | {/*

可视化编辑器

*/} 19 | 24 | {/*
{JSON.stringify(data)}
*/} 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build" 7 | }, 8 | "dependencies": { 9 | "@ant-design/icons": "^4.5.0", 10 | "antd": "^4.15.0", 11 | "classnames": "^2.2.6", 12 | "deepcopy": "^2.1.0", 13 | "react": "^17.0.1", 14 | "react-color": "^2.19.3", 15 | "react-dom": "^17.0.1" 16 | }, 17 | "devDependencies": { 18 | "@types/classnames": "^2.2.11", 19 | "@types/node": "^14.14.37", 20 | "@types/react": "^17.0.2", 21 | "@types/react-color": "^3.0.4", 22 | "@types/react-dom": "^17.0.1", 23 | "@vitejs/plugin-react-refresh": "^1.3.1", 24 | "less": "^4.1.1", 25 | "sass": "^1.42.0", 26 | "typescript": "^4.1.5", 27 | "vite": "^2.0.1", 28 | "vite-plugin-babel-import": "^2.0.5", 29 | "vite-plugin-style-import": "^1.2.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/packages/plugin/keyboard-code.ts: -------------------------------------------------------------------------------- 1 | export const KeyboardCode = { 2 | 16: 'shift', 3 | 17: 'ctrl', 4 | 18: 'alt', 5 | 6 | 8: 'backspace', 7 | 9: 'tab', 8 | 13: 'enter', 9 | 27: 'esc', 10 | 32: 'space', 11 | 12 | 37: 'left', 13 | 38: 'up', 14 | 39: 'right', 15 | 40: 'down', 16 | 46: 'delete', 17 | 189: '-', 18 | 187: '=', 19 | 20 | 48: '0', 21 | 49: '1', 22 | 50: '2', 23 | 51: '3', 24 | 52: '4', 25 | 53: '5', 26 | 54: '6', 27 | 55: '7', 28 | 56: '8', 29 | 57: '9', 30 | 31 | 65: 'a', 32 | 66: 'b', 33 | 67: 'c', 34 | 68: 'd', 35 | 69: 'e', 36 | 70: 'f', 37 | 71: 'g', 38 | 72: 'h', 39 | 73: 'i', 40 | 74: 'j', 41 | 75: 'k', 42 | 43 | 76: 'l', 44 | 77: 'm', 45 | 78: 'n', 46 | 79: 'o', 47 | 80: 'p', 48 | 81: 'q', 49 | 82: 'r', 50 | 83: 's', 51 | 84: 't', 52 | 85: 'u', 53 | 86: 'v', 54 | 87: 'w', 55 | 88: 'x', 56 | 89: 'y', 57 | 90: 'z', 58 | 59 | 112: 'F1', 60 | 113: 'F2', 61 | 114: 'F3', 62 | 115: 'F4', 63 | 116: 'F5', 64 | 117: 'F6', 65 | 118: 'F7', 66 | 119: 'F8', 67 | 120: 'F9', 68 | 121: 'F10', 69 | 122: 'F11', 70 | 123: 'F12', 71 | } as Record -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | import { defineConfig } from 'vite'; 4 | import styleImport from 'vite-plugin-style-import'; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | reactRefresh(), 9 | // styleImport({ 10 | // libs: [ 11 | // { 12 | // libraryName: 'antd', 13 | // esModule: true, 14 | // resolveStyle: (name) => { 15 | // return `antd/es/${name}/style`; 16 | // }, 17 | // } 18 | // ] 19 | // }), 20 | ], 21 | // css: { 22 | // preprocessorOptions: { 23 | // less: { 24 | // javascriptEnabled: true 25 | // } 26 | // } 27 | // }, 28 | esbuild: { 29 | jsxInject: "import React from 'react'", // 为每个 tsx jsx 自动引入 React,不用手动引入了 30 | }, 31 | resolve: { 32 | alias: { 33 | "@": path.resolve(__dirname, "src"), 34 | "@assets": path.resolve(__dirname, "src/assets"), 35 | "@components": path.resolve(__dirname, "src/components"), 36 | "@images": path.resolve(__dirname, "src/assets/images"), 37 | "@views": path.resolve(__dirname, "src/views"), 38 | "@store": path.resolve(__dirname, "src/store"), 39 | } 40 | }, 41 | server: { 42 | https: false, // 是否开启 https 43 | open: true, // 是否自动在浏览器打开 44 | port: 3000, // 端口号 45 | host: "0.0.0.0", 46 | hmr: { 47 | overlay: true, // 是否开启错误的阴影层 48 | } 49 | }, 50 | optimizeDeps: { 51 | include: [] // 第三方库 52 | }, 53 | build: { 54 | chunkSizeWarningLimit: 2000, 55 | terserOptions: { 56 | // 生产环境移除console 57 | compress: { 58 | drop_console: true, 59 | drop_debugger: true, 60 | }, 61 | }, 62 | rollupOptions: { 63 | output:{ 64 | // manualChunks(id) { 65 | // if (id.includes('node_modules')) { 66 | // return id.toString().split('node_modules/')[1].split('/')[0].toString(); 67 | // } 68 | // } 69 | manualChunks: { 70 | react: ['react', 'react-dom'], 71 | antd: ['antd'] 72 | } 73 | } 74 | } 75 | } 76 | 77 | }) 78 | -------------------------------------------------------------------------------- /src/packages/EditorBlock.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useRef } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | import { VisualEditorBlockData, VisualEditorConfig } from './editor.utils'; 5 | import { useUpdate } from './hook/useUpdate'; 6 | 7 | import classModule from './style/VisualEditorBlock.module.scss'; 8 | 9 | export const VisualEditorBlock: React.FC<{ 10 | block: VisualEditorBlockData, 11 | config: VisualEditorConfig, 12 | onMouseDown?: (e: React.MouseEvent) => void, 13 | onContextMenu?: (e: React.MouseEvent) => void, 14 | editing?: boolean, 15 | preview?: boolean, 16 | }> = (props) => { 17 | // 强制更新一次 18 | const { froceUpdate } = useUpdate(); 19 | const elRef = useRef({} as HTMLDivElement); 20 | 21 | const style = useMemo(() => { 22 | return { 23 | top: `${props.block.top}px`, 24 | left: `${props.block.left}px`, 25 | zIndex: props.block.zIndex, 26 | opacity: props.block.adjustPosition ? 0 : "", // 解决调整拖拽结束时居中时组件闪动 BUG 27 | } 28 | }, [props.block.top, props.block.left, props.block.zIndex]); 29 | 30 | const classes = useMemo(() => classNames([ 31 | classModule['visual-editor__block'], 32 | { [classModule['editor-block__mask']]: props.preview ? false : props.editing }, 33 | { [classModule['editor-block__active']]: props.preview ? false : (props.editing ? props.block.focus : false) }]) 34 | , [props.block.focus, props.editing, props.preview]) 35 | 36 | const component = props.config.componentMap[props.block.componentKey]; 37 | 38 | let render: any; 39 | if (!!component) { 40 | render = component.render({} as any); 41 | } 42 | 43 | useEffect(() => { 44 | if (props.block.adjustPosition) { 45 | // 设置是首次拖到容器中是否调整位置居于鼠标点 46 | const { top, left } = props.block; 47 | const blockCom = elRef.current; 48 | const { width, height } = blockCom.getBoundingClientRect(); 49 | props.block.adjustPosition = false; 50 | props.block.left = left - width / 2; 51 | props.block.top = top - height / 2; 52 | 53 | // 记录 block 组件的高度和宽度 54 | props.block.width = blockCom.offsetWidth; 55 | props.block.height = blockCom.offsetHeight; 56 | 57 | froceUpdate(); // 需要引用一次才可以更新视图 58 | } 59 | }, []); 60 | 61 | return (() => { 62 | 63 | return ( 64 |
71 | {render} 72 |
73 | ) 74 | })() 75 | } -------------------------------------------------------------------------------- /src/packages/editor.utils.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 容器中每个元素的的数据类型 3 | */ 4 | export interface VisualEditorBlockData { 5 | componentKey: string, // component 对象的的 key,通过该值找到 visual config 中的对应的 component 6 | top: number, // block 在容器中的 top 位置 7 | left: number, // block 在容器中的 left 位置 8 | width: number, // block 组件自身的宽度 9 | height: number, // block 组件自身的高度 10 | adjustPosition: boolean, // 添加组件到容器中时是否需要调整位置 11 | focus: boolean, // 组件是否是选中状态 12 | zIndex: number, // block 组件元素的 z-index style 属性 13 | hasReasize: boolean, // block 组件元素是否调整大小 14 | props?: Record // block 组件元素右侧属性配置信息 15 | model?: Record // 组件元素右侧自定义配置属性信息(绑定值) 16 | slotName?: string // 组件标识 17 | } 18 | /** 19 | * 编辑器编辑的数据类型 20 | */ 21 | export interface VisualEditorValue { 22 | container: { // 画布容器 23 | height: number, 24 | width: number, 25 | }, 26 | blocks: VisualEditorBlockData[] 27 | } 28 | 29 | /** 30 | * 编辑器中自定义组件的类型 31 | */ 32 | export interface VisualEditorComponent { 33 | key: string, // key 组件唯一标识符 34 | label: string, // label 组件左侧显示名 35 | render: (data: { // render 组件渲染函数,拖拽后在容器区与呈现的函数 36 | block: VisualEditorBlockData, 37 | size: {width?: string, height?: string}, 38 | }) => JSX.Element, 39 | preview: () => JSX.Element, // preview 组件左侧预览函数 40 | } 41 | 42 | 43 | /** 44 | * 创建一个 block 的组件数据 45 | */ 46 | export function createVisualBlock({ 47 | top, 48 | left, 49 | component 50 | }: { 51 | top: number, 52 | left: number, 53 | component: VisualEditorComponent 54 | }): VisualEditorBlockData { 55 | return { 56 | adjustPosition: true, 57 | componentKey: component.key, 58 | top, 59 | left, 60 | width: 0, 61 | height: 0, 62 | focus: false, 63 | zIndex: 0, 64 | hasReasize: false 65 | } 66 | } 67 | 68 | 69 | /** 70 | * 创建编辑器的预设内容 71 | */ 72 | export function createVisualConfig() { 73 | // 用于 block 数据,通过 componentKey 找到 component 对象,使用 component 对象的 render 属性渲染内容到 container 容器里 74 | const componentMap: { [k: string]: VisualEditorComponent } = {}; 75 | // 用户在 menu 中预定义的组件列表 76 | const componentList: VisualEditorComponent[] = []; 77 | 78 | const registryComponent = (key: string, options: Omit) => { 79 | // key 是唯一的 80 | if (componentMap[key]) { 81 | const index = componentList.indexOf(componentMap[key]); 82 | componentList.splice(index, 1); 83 | } 84 | const newComponent = { 85 | key, 86 | ...options 87 | } 88 | 89 | componentList.push(newComponent); 90 | componentMap[key] = newComponent; 91 | } 92 | 93 | return { 94 | componentList, 95 | componentMap, 96 | registryComponent 97 | } 98 | } 99 | 100 | export type VisualEditorConfig = ReturnType; -------------------------------------------------------------------------------- /src/packages/service/dropdown/$$dropdown.tsx: -------------------------------------------------------------------------------- 1 | import './$$dropdown.scss'; 2 | import ReactDOM from "react-dom"; 3 | import { MouseEvent, useContext, useEffect, useMemo, useRef, useState } from 'react'; 4 | import { useCallbackRef } from '../../hook/useCallbackRef'; 5 | 6 | type Reference = { x: number, y: number } | MouseEvent | HTMLElement; 7 | type Render = JSX.Element | JSX.Element[] | React.ReactFragment; 8 | 9 | interface DropdownOption { 10 | reference: Reference, 11 | render: () => Render, 12 | } 13 | 14 | const DropdownContext = React.createContext<{onClick: () => void}>({} as any); 15 | 16 | const DropdownComponent: React.FC<{ 17 | option: DropdownOption, 18 | onRef: (ins: { show: (opt: DropdownOption) => void, close: () => void }) => void 19 | }> = (props) => { 20 | 21 | const elRef = useRef({} as HTMLDivElement); 22 | 23 | const [option, setOption] = useState(props.option); 24 | const [showFlag, setShowFlag] = useState(false); 25 | 26 | const styles = useMemo(() => { 27 | let x = 0, y = 0; 28 | const reference = option.reference 29 | if ('target' in reference) { 30 | x = reference.clientX - 20; 31 | y = reference.clientY - 20; 32 | } else if ('addEventListener' in reference) { 33 | const { top, left, height } = reference.getBoundingClientRect(); 34 | x = left; 35 | y = top + height; 36 | } else { 37 | x = reference.x; 38 | y = reference.y; 39 | } 40 | return { 41 | left: `${x + 20}px`, 42 | top: `${y + 20}px`, 43 | display: showFlag ? 'inline-block' : 'none' 44 | } 45 | }, [option?.reference, showFlag]); 46 | 47 | const methods = { 48 | show: (opt: DropdownOption) => { 49 | setOption(opt); 50 | setShowFlag(true); 51 | }, 52 | close: () => { 53 | setShowFlag(false); 54 | } 55 | }; 56 | 57 | const handler = { 58 | onClickBody: useCallbackRef((ev: MouseEvent) => { 59 | if (elRef.current.contains(ev.target as Node)) { 60 | /*点击了dropdown content*/ 61 | return; 62 | } else { 63 | methods.close(); 64 | } 65 | }), 66 | onClickDropItem: useCallbackRef(() => { 67 | methods.close(); 68 | }) 69 | }; 70 | 71 | props.onRef!(methods); 72 | 73 | useEffect(() => { 74 | document.body.addEventListener('click', handler.onClickBody as any); 75 | return () => { 76 | document.body.removeEventListener('click', handler.onClickBody as any); 77 | } 78 | }, []) 79 | 80 | return (<> 81 | 82 |
83 | {option?.render()} 84 |
85 |
86 | ); 87 | } 88 | 89 | export const DropdownOption: React.FC<{ 90 | onClick?: (ev: React.MouseEvent) => void 91 | icon?: string, 92 | label?: string 93 | }> = (props) => { 94 | 95 | const dropdown = useContext(DropdownContext); 96 | const handler = { 97 | onClick: (e: React.MouseEvent) => { 98 | dropdown.onClick(); 99 | props.onClick && props.onClick(e); 100 | } 101 | }; 102 | 103 | return (
104 | {props.icon && } 105 | {props.label && {props.label}} 106 | {/* {props.children} 这个时元素默认的标签内的内容*/} 107 |
); 108 | } 109 | 110 | export const $$dropdown = (() => { 111 | 112 | let ins: any; 113 | return (options: DropdownOption) => { 114 | if (!ins) { 115 | const el = document.createElement('div'); 116 | document.body.appendChild(el); 117 | ReactDOM.render( ins = val} />, el); 118 | } 119 | 120 | ins.show(options); 121 | } 122 | })(); 123 | 124 | -------------------------------------------------------------------------------- /src/packages/service/dialog/$$dialog.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Button, Input, Modal } from "antd"; 3 | import ReactDOM from "react-dom"; 4 | import deepcopy from 'deepcopy'; 5 | import { defer } from "../../utils/defer"; 6 | 7 | export enum DialogEdit { 8 | input = 'input', 9 | textarea = 'textarea', 10 | } 11 | 12 | interface DialogOption { 13 | title?: string, 14 | message?: string | (() => any), 15 | confirmButton?: boolean 16 | cancelButton?: boolean 17 | editType?: DialogEdit, 18 | editValue?: string, 19 | editReadonly?: boolean 20 | width?: string, 21 | onConfirm?: (editValue?: string) => void, 22 | onCancel?: () => void, 23 | } 24 | 25 | interface DialogInstance { 26 | show: (option?: DialogOption) => void, 27 | close: () => void, 28 | } 29 | 30 | const DialogComponent: React.FC<{ option?: DialogOption, onRef?: (ins: DialogInstance) => void }> = (props) => { 31 | 32 | let [option, setOption] = useState(props.option || {}); 33 | const [showFlag, setShowFlag] = useState(false); 34 | const [editValue, setEditValue] = useState(option ? option.editValue : ''); 35 | 36 | const methods = { 37 | show: (option?: DialogOption) => { 38 | setOption(deepcopy(option || {})); 39 | setEditValue(!option ? '' : (option.editValue || '')); 40 | setShowFlag(true); 41 | }, 42 | close: () => setShowFlag(false), 43 | } 44 | 45 | props.onRef && props.onRef(methods); 46 | 47 | const handler = { 48 | onConfirm: () => { 49 | option.onConfirm && option.onConfirm(editValue) 50 | methods.close() 51 | }, 52 | onCancel: () => { 53 | option.onCancel && option.onCancel() 54 | methods.close() 55 | }, 56 | }; 57 | 58 | const inputProps = { 59 | value: editValue, 60 | onChange: (e: React.ChangeEvent) => setEditValue(e.target.value), 61 | readOnly: option.editReadonly === true, 62 | }; 63 | 64 | return ( 65 | 73 | {option.cancelButton && } 74 | {option.confirmButton && } 75 | 76 | ) : null} 77 | > 78 | {option.message} 79 | {option.editType === DialogEdit.input && ( 80 | 81 | )} 82 | {option.editType === DialogEdit.textarea && ( 83 | 84 | )} 85 | 86 | ) 87 | 88 | } 89 | 90 | const getInstance = (() => { 91 | let ins: null | DialogInstance = null; 92 | return (option?: DialogOption) => { 93 | if (!ins) { 94 | const el = document.createElement('div'); 95 | document.body.appendChild(el); 96 | ReactDOM.render( ins = val} />, el); 97 | } 98 | return ins!; 99 | } 100 | })(); 101 | 102 | const DialogService = (option?: DialogOption) => { 103 | const ins = getInstance(option); 104 | ins.show(option); 105 | } 106 | 107 | export const $$dialog = Object.assign(DialogService, { 108 | textarea: (val?: string, option?: DialogOption) => { 109 | const dfd = defer(); 110 | option = option || {}; 111 | option.editType = DialogEdit.textarea; 112 | option.editValue = val; 113 | if (option.editReadonly !== true) { 114 | option.confirmButton = true; 115 | option.cancelButton = true; 116 | option.onConfirm = dfd.resolve; 117 | } 118 | DialogService(option); 119 | return dfd.promise; 120 | }, 121 | input: (val?: string, option?: DialogOption) => { 122 | // TODO 123 | console.log(val, option) 124 | }, 125 | }) -------------------------------------------------------------------------------- /src/asset/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1610438286453'); /* IE9 */ 3 | src: url('iconfont.eot?t=1610438286453#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAfEAAsAAAAADxAAAAd2AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCEUgqPDItvATYCJAM0CxwABCAFhG0HgRsbjgwRFaz7IPsiwbaFkDcaa05axJnHSYukNfk8/Db/vgAfrxitD6OPrqJhVcYqdWvUFfCDs0/4WOkaXEWBq/r4S361BkAMMBiDSfPm99uvX7MSCOVp3Pqta0cu8HQ4w6z9//u5OlUIyTZvC4lDLV/83ccZahISSUPSLK4pWSu0Siz4z/Dq1Q6uyX/iSRcDAYBFIuqD6N6z/3AwoCElAk1mTsufBCYSDzrHToCJURIH0nrCBQUY0k7eBfCl8/XE92hKMAAJBQW54+C8HjnoHFF+O4uy1dow1pKBaS8VwHI1gAJQHwC9xIfJ4F4AEftQCiyi66QaUUcElRF1vl6R/MjsyLzIooi9qk3VgG9n1daKaHL7QygBY1Rpf4ACCQYqsODAI2b+SDGuP54SEvEYURZJiCiNWFB76IVYkIBcxIIB5CEWKkA+YsECZiMWHGAe6PZgEWIRA7DDDwRQ1cYPSqBqgB8UwLez2AcaCVZrCUAKQFp5QhjSlINSAhRo2CmQ6QrQiRWI2HSVSsnSNM9Pka5XS1JmTGwywwhCHZ5XZYbum7kgz3t0kyE3x735mCk/13NiqzEv37fluH4ZS3i9rLkR73CDtHkMHDzsQpzg9BFUkXem4POZKI7kZ/GEv/YUgeJo0cpBoFhCO5dCTc8WKH6ZM2Dbw4qOYFEFR1hInRhJUrvM4Y8/TlRsfdJK8Hhs7h5L98TScqAfWcUOPp/TA3LTwzzW6vUajdNsNOVXOLMtAVtWzh6rxWnK9xURrR36HLdh2XC3Izi9z8SFw9bw0+HWyo+WUMjCeeSlFk+pnmW1Q5UjLE+fWvPuwnq9phE+5xzea1T4TALkcxkcbAFnhYnoIlfP7zg7AP25jNMWBSeA9RoJLpcd5isKOvTD3TaDxZOaZhzKZZEXK9d/PpMt3Q6sY48twHG18ndfMCvyxMnDWd9jADvNpue9hELI6aE06NgDFCRnGEsUhX4tPTyBKeEXkte08eWI0PM4T2zps/mHQpbKymaHw9anTyddD6dur+x1LZRS9tS646ztnPNC0fll4ojhtLWqPQbSzYruQSxHtfOCmfKfF+S5LN9jMwxha6dyg91lyfPk5l8mOoxUonp4jL7kZKFYiyjkpFy90i7l3qzsall+k52lct/IcrVdlR31B6LRHdH60J8FJdwbvqSQjXJVvLHuN1z0YEkB9/pgwWp2T+F1Sn7oUxZlrSVVVYVbqlZbD77m+hSg7cyHT4qHDi2o7tgmHG7TsdAXCpAz1ND2pTwJmjU5Yjqf9caQZ3iTdd6Ub3idba2TZzqf/drQ3HQum7ph9jkTPimqbVZGzVhtUlm1KcpJWjuhJuzaaq14+Xps1Bjl6jFWQkmPom13GE1OgjOQkKBtltqnj6aZJilRM751d5pOTUxKiUnt20dr/lcJ8WkU+lzaVlJcaFxXXLx35drFcUsqti/RL9WE8jvM6BLXuxgrvzJsiLtzS9qQszq9XWNdSXF+8zXzU2bucs3VzFXfGtF5XJe4fot+m23X243Xj6Wtnbp25d7i4nUGbEz62aUKqlwtykT/T2WZgtas5dcKS4IB+aLnJ1kdVMs//ZxaSZuSoSlzqcp0KUdrmtrjUuNKp0+3T2WfPk02p8XaLTWixqxBy8Nf3ulZWAxWiut5Z72m0DJdLb8Lv5PV6S2jp6/soXIFXaqeF4dPYV1hF5v8XZ+goOVDQlVBtOpoT4paIdQOgRfgtaht90Tznh8zbsKYD8JjzRPte3HM2MGj3vODWu28fHMzqwgk7dzl1Zx4o3zAAEkycLlT3vbJu2BHtTNaDHVOHDdOlpqZZZfLLjnMvWRzc0keOsziUlWo2ODDrn9SMNkv7hP96JG08WLoN4rqgpM0/H/69NLYpnFyas3FFHOaUJMmR1aV9kybNlWFFoOdQbb51q4RQ4fJUnOzv1dRMlcvNTeT5HHjJv/kFyvEs0W2VZ6EVX9B3otaSEaA2he7qEygNvo51RAAqHzS5qw2nfwVtQfoHdQwFvaugEpZ/62TSgYA6gr5mKWXUal5ihmNAGoJlf63shH+vpu3c4K6/e8M9/Gf8iqt8AvBtVsIBuS7H/An0o2uAUDvWHFSFI7jw5hIDcW22QEC+kIBfAFjwapsoJZna/GmH2wozfxnYUPgIBGDBAcFBulyWl3foQCH5g4lGHRysKiHXodz0GE+KBC0CkBdrGEOAnVwwEFCxAWRotT3RBrq1w4FjKgWlDATlIOFldCdkEMmge9ObwwtcMq8VGdp9nqSnNym3wi3UTPntK3+wbmjWTVlfar6wgxuY5d8D62IV55pUp9sB4wjqcTUw0oZUZmuVeXXu5aWpsVp0Y2hxfJ0yrwCO0uzn1xOic//RriNmnP63Af+g3M3f9WUdQn5F5pL9TmW1vkeWuGpXhmlTJOHP0UqRlVOKtHr9bBSxhql6VrxsXxZVPaPp/t8DgC7kB8rLIuq6YZp2Y7rST8UdqSMYxq1RSGULt4LGBKh6YRZXokmzLcNXCdbRobsuikR22dzGCHYGaZHxsZoO+zwJNfYe+KHZrdYAAA=') format('woff2'), 5 | url('iconfont.woff?t=1610438286453') format('woff'), 6 | url('iconfont.ttf?t=1610438286453') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1610438286453#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-close:before { 19 | content: "\e689"; 20 | } 21 | 22 | .icon-place-top:before { 23 | content: "\e647"; 24 | } 25 | 26 | .icon-place-bottom:before { 27 | content: "\e606"; 28 | } 29 | 30 | .icon-entypomenu:before { 31 | content: "\e96d"; 32 | } 33 | 34 | .icon-edit:before { 35 | content: "\e74d"; 36 | } 37 | 38 | .icon-reset:before { 39 | content: "\e739"; 40 | } 41 | 42 | .icon-import:before { 43 | content: "\e675"; 44 | } 45 | 46 | .icon-delete:before { 47 | content: "\e665"; 48 | } 49 | 50 | .icon-browse:before { 51 | content: "\e666"; 52 | } 53 | 54 | .icon-back:before { 55 | content: "\e667"; 56 | } 57 | 58 | .icon-export:before { 59 | content: "\e66e"; 60 | } 61 | 62 | .icon-forward:before { 63 | content: "\e671"; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/packages/style/VisualEditor.module.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "../../asset/font/iconfont.css"; 3 | @import './_variable.scss'; 4 | 5 | .visual-editor__container { 6 | position: fixed; 7 | top: $gap; 8 | left: $gap; 9 | right: $gap; 10 | bottom: $gap; 11 | &::before { 12 | content: ''; 13 | position: fixed; 14 | top: 0; 15 | bottom: 0; 16 | left: 0; 17 | right: 0; 18 | background-color: #ccc; 19 | } 20 | .visual-editor__menu, 21 | .visual-editor__head, 22 | .visual-editor__operator, 23 | .visual-editor__body { 24 | position: absolute; 25 | top: $headSize; 26 | left: $menuSize; 27 | right: $operatorSize; 28 | bottom: 0; 29 | box-sizing: border-box; 30 | padding: 2px; 31 | background-color: #f6f6f6; 32 | overflow: auto; 33 | &.visual-editor__menu { 34 | top: 0; 35 | left: 0; 36 | bottom: 0; 37 | width: $menuSize; 38 | padding: 20px; 39 | border-right: 1px solid $ibc; 40 | background-color:#fff; 41 | box-sizing: border-box; 42 | user-select: none; 43 | .visual-editor__menu__title { 44 | font-size: 22px; 45 | color: $primary; 46 | border-bottom: 1px solid $ibc; 47 | } 48 | .editor-menu__item { 49 | position: relative; 50 | border: 2px solid $ibc; 51 | height: 80px; 52 | margin: 20px 0; 53 | &::after { 54 | content: ''; 55 | position: absolute; 56 | top: 0; 57 | left: 0; 58 | bottom: 0; 59 | right: 0; 60 | } 61 | .menu-item__title { 62 | position: absolute; 63 | color: #fff; 64 | background-color: $primary; 65 | padding: 2px 5px; 66 | } 67 | .menu-item__content { 68 | position: absolute; 69 | top: 50%; 70 | left: 50%; 71 | transform: translate(-50%, -50%); 72 | margin: 5px 5px 0 5px; 73 | } 74 | } 75 | } 76 | &.visual-editor__head { 77 | top: 0; 78 | left: $menuSize; 79 | right: $operatorSize; 80 | height: $headSize;; 81 | background-color: #fff; 82 | border-bottom: 1px solid $ibc; 83 | display: flex; 84 | justify-content: center; 85 | align-items: center; 86 | .editor-head__button { 87 | position: relative; 88 | display: flex; 89 | align-items: center; 90 | justify-content: center; 91 | flex-direction: column; 92 | text-align: center; 93 | background-color: rgba(black, 0.3); 94 | color: #fff; 95 | padding: 3px 10px; 96 | transition: all linear 0.1s; 97 | cursor: pointer; 98 | user-select: none; 99 | & > span { 100 | font-size: 12px; 101 | } 102 | &:not(:last-child) { 103 | &::after { 104 | content: ''; 105 | position: absolute; 106 | top: 0; 107 | right: 0; 108 | bottom: 0; 109 | width: 1px; 110 | background-color: rgba(white, 0.4); 111 | } 112 | } 113 | &:hover { 114 | background-color: #fff; 115 | color: $primary; 116 | box-shadow: 0px 0px 0px 0.5px $primary; 117 | } 118 | &:active { 119 | background-color: mix($primary, white, 10%); 120 | } 121 | } 122 | } 123 | &.visual-editor__operator { 124 | top: 0; 125 | right: 0; 126 | bottom: 0; 127 | width: $operatorSize; 128 | background-color: #fff; 129 | box-sizing: border-box; 130 | border-left: 1px solid $ibc; 131 | } 132 | } 133 | } 134 | 135 | .editor-body_container { 136 | position: relative; 137 | margin: 0 auto; 138 | background-color: #fff; 139 | box-sizing: border-box; 140 | &.edit-container__border { 141 | border: 1px dashed $primary; 142 | } 143 | .editor-mark-x, 144 | .editor-mark-y { 145 | position: absolute; 146 | &.editor-mark-x { 147 | top: 0; 148 | bottom: 0; 149 | left: 5px; 150 | border-left: 1px dashed $primary; 151 | } 152 | &.editor-mark-y { 153 | left: 0; 154 | right: 0; 155 | top: 5px; 156 | border-top: 1px dashed $primary; 157 | } 158 | } 159 | } 160 | 161 | .visual-editor__preview { 162 | position: relative; 163 | height: inherit; 164 | width: 100%; 165 | background-color: rgba(black, 0.1); 166 | .editor-preview__edit { 167 | position: fixed; 168 | right: 2%; 169 | top: 2%; 170 | z-index: 10; 171 | } 172 | .preview-edit__warpper { 173 | position: absolute; 174 | top: 50%; 175 | left: 50%; 176 | transform: translate(-50%, -50%); 177 | } 178 | } 179 | 180 | 181 | /* 自定义滚动条样式*/ 182 | .custom-bar__style { 183 | &::-webkit-scrollbar { 184 | width: 8px; 185 | height: 8px; 186 | } 187 | &::-webkit-scrollbar-thumb { 188 | border-radius: 1em; 189 | background-color: rgba(0, 0, 0, 0.1); 190 | } 191 | &::-webkit-scrollbar-track { 192 | border-radius: 1em; 193 | background-color: transparent; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "container": { 3 | "height": 500, 4 | "width": 600 5 | }, 6 | "blocks": [ 7 | { 8 | "adjustPosition": false, 9 | "componentKey": "text", 10 | "top": 69, 11 | "left": 244, 12 | "width": 56, 13 | "height": 22, 14 | "focus": false, 15 | "zIndex": 0, 16 | "hasReasize": false, 17 | "props": { 18 | "text": "登录界面", 19 | "color": { 20 | "hsl": { 21 | "h": 212.3684210526316, 22 | "s": 0.7238095238095237, 23 | "l": 0.5882352941176471, 24 | "a": 1 25 | }, 26 | "hex": "#4a90e2", 27 | "rgb": { 28 | "r": 74, 29 | "g": 144, 30 | "b": 226, 31 | "a": 1 32 | }, 33 | "hsv": { 34 | "h": 212.3684210526316, 35 | "s": 0.6725663716814159, 36 | "v": 0.8862745098039215, 37 | "a": 1 38 | }, 39 | "oldHue": 250, 40 | "source": "hex" 41 | }, 42 | "size": "18px" 43 | } 44 | }, 45 | { 46 | "adjustPosition": false, 47 | "componentKey": "button", 48 | "top": 360, 49 | "left": 292.5, 50 | "width": 88, 51 | "height": 32, 52 | "focus": false, 53 | "zIndex": 0, 54 | "hasReasize": false, 55 | "slotName": "buttonComponent", 56 | "props": { 57 | "label": "提交" 58 | } 59 | }, 60 | { 61 | "adjustPosition": false, 62 | "componentKey": "input", 63 | "top": 143, 64 | "left": 186, 65 | "width": 135, 66 | "height": 32, 67 | "focus": false, 68 | "zIndex": 0, 69 | "hasReasize": false, 70 | "model": { 71 | "default": "username" 72 | } 73 | }, 74 | { 75 | "adjustPosition": false, 76 | "componentKey": "select", 77 | "top": 212, 78 | "left": 186, 79 | "width": 172, 80 | "height": 32, 81 | "focus": false, 82 | "zIndex": 0, 83 | "hasReasize": true, 84 | "props": { 85 | "options": [ 86 | { 87 | "key": "1617589791638_1", 88 | "label": "喵喵", 89 | "val": "cat", 90 | "comments": "喵喵" 91 | }, 92 | { 93 | "key": "1617589791638_0", 94 | "label": "狗狗", 95 | "val": "dog", 96 | "comments": "狗狗" 97 | } 98 | ] 99 | }, 100 | "model": { 101 | "default": "aaaa" 102 | } 103 | }, 104 | { 105 | "adjustPosition": false, 106 | "componentKey": "number-range", 107 | "top": 287, 108 | "left": 186, 109 | "width": 225, 110 | "height": 26, 111 | "focus": false, 112 | "zIndex": 0, 113 | "hasReasize": false, 114 | "model": { 115 | "start": "minLevel", 116 | "end": "maxLevel" 117 | } 118 | }, 119 | { 120 | "adjustPosition": false, 121 | "componentKey": "button", 122 | "top": 360, 123 | "left": 180.5, 124 | "width": 88, 125 | "height": 32, 126 | "focus": false, 127 | "zIndex": 0, 128 | "hasReasize": false, 129 | "props": { 130 | "label": "取消", 131 | "type": "ghost" 132 | } 133 | }, 134 | { 135 | "adjustPosition": false, 136 | "componentKey": "text", 137 | "top": 148, 138 | "left": 92, 139 | "width": 56, 140 | "height": 22, 141 | "focus": false, 142 | "zIndex": 0, 143 | "hasReasize": false, 144 | "props": { 145 | "text": "用户名:" 146 | } 147 | }, 148 | { 149 | "adjustPosition": false, 150 | "componentKey": "text", 151 | "top": 222, 152 | "left": 92, 153 | "width": 56, 154 | "height": 22, 155 | "focus": false, 156 | "zIndex": 0, 157 | "hasReasize": false, 158 | "props": { 159 | "text": "喜好:" 160 | } 161 | }, 162 | { 163 | "adjustPosition": false, 164 | "componentKey": "text", 165 | "top": 289, 166 | "left": 85, 167 | "width": 56, 168 | "height": 22, 169 | "focus": false, 170 | "zIndex": 0, 171 | "hasReasize": false, 172 | "props": { 173 | "text": "幸运数字:" 174 | } 175 | }, 176 | { 177 | "adjustPosition": false, 178 | "componentKey": "image", 179 | "top": 164, 180 | "left": 434, 181 | "width": 100, 182 | "height": 100, 183 | "focus": false, 184 | "zIndex": 0, 185 | "hasReasize": false 186 | } 187 | ] 188 | } -------------------------------------------------------------------------------- /src/packages/plugin/command.plugin.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef, useState } from "react"; 2 | import { KeyboardCode } from "./keyboard-code"; 3 | 4 | // command 的 execute 执行完之后,需要返回 undo、redo。execute 执行后会立即返回 redo,后续撤销的时候会执行 undo,重做的时候会执行 redo 5 | interface CommandExecute { 6 | redo: () => void, // 默认执行,重做会调用 7 | undo?: () => void, // 撤销会调用 8 | } 9 | 10 | interface Command { 11 | name: string, // 命令的唯一标识 12 | execute: (...args: any[]) => CommandExecute, // 命令执行时候,所处理的内容 13 | keyboard?: string | string[], // 命令监听的快捷键 14 | followQueue?: boolean, // 命令执行之后,是否需要将命令执行得到的 undo,redo 存入命令队列(像全选、撤销、重做这中命令不需要存入命令队列的) 15 | init?: () => ((() => void) | undefined), // 命令初始化函数,如果返回的,则是一个销毁命令函数 16 | data?: any // 命令缓存所需的数据信息 17 | } 18 | 19 | export function useCommander() { 20 | 21 | const [state] = useState(() => ({ 22 | current: -1, // 当前命令队列中,最后执行的命令返回的 CommandExecute 对象 23 | queue: [] as CommandExecute[], // 命令队列容器 24 | commandList: [] as { current: Command }[], // 预定义命令的容器 25 | commands: {} as Record void>, // 通过 command name 执行 command 动作的一个包装对象 26 | destroyList: [] as ((() => void) | undefined)[] // 所有命令在组件销毁之前,需要执行的消除副作用的函数容器 27 | })); 28 | 29 | /** 30 | * 注册命令 31 | */ 32 | const useRegistry = useCallback((command: Command) => { 33 | const commandRef = useRef(command); 34 | commandRef.current = command; 35 | useState(() => { 36 | // 判断命令是否存在 37 | if (state.commands[command.name]) { 38 | const existIndex = state.commandList.findIndex(item => item.current.name === command.name); 39 | state.commandList.splice(existIndex, 1); 40 | } 41 | state.commandList.push(commandRef); 42 | // 对应命令的方法 AAAAAAA 43 | state.commands[command.name] = (...args: any[]) => { 44 | const { redo, undo } = commandRef.current.execute(...args); 45 | // 默认执行重做 46 | redo(); 47 | // 如果命令执行后,不需要进入命令队列,就直接结束 48 | if (commandRef.current.followQueue === false) { 49 | return; 50 | } 51 | // 否则,将命令队列中剩余的命令都删除,保留 current 及其之前的命令 52 | let { queue, current } = state; 53 | if (queue.length > 0) { 54 | queue = queue.slice(0, current + 1); 55 | state.queue = queue; 56 | } 57 | // 将命令队列中最后一个命令为i当前执行的命令 58 | queue.push({ undo, redo }); 59 | // 索引加 1, 指向队列中的最有一个命令 60 | state.current = current + 1; 61 | } 62 | /** 63 | * commands 结构类型 64 | * { 65 | * undo: () => {}, 66 | * redo: () => {}, 67 | * delete: () => {}, 68 | * clear: () => {}, 69 | * placeTop: () => {}, 70 | * placeBottom: () => {} 71 | * } 72 | */ 73 | 74 | }); 75 | }, []); 76 | 77 | // 快捷键 78 | const [keyboardEvent] = useState(() => { 79 | const onKeydown = (ev: KeyboardEvent) => { 80 | // 对于容器是否在空白区域或时操作某个组件的命令区分操作,比如空白区域时全选或全中所有的组件组件,在操作某个输入框组件时,全选就只会选中输入框中的文字 81 | if (document.activeElement !== document.body) { 82 | return; 83 | } 84 | const { keyCode, shiftKey, altKey, ctrlKey, metaKey } = ev; 85 | 86 | let keyString: string[] = []; 87 | 88 | if (ctrlKey || metaKey) { 89 | keyString.push('ctrl'); 90 | } 91 | if (shiftKey) { 92 | keyString.push('shift'); 93 | } 94 | if (altKey) { 95 | keyString.push('alt'); 96 | } 97 | keyString.push(KeyboardCode[keyCode]); 98 | 99 | // 快捷键格式 'ctrl+alt+s' 100 | const keyNames = keyString.join('+'); 101 | 102 | state.commandList.forEach(({ current: { keyboard, name } }) => { 103 | if (!keyboard) return; 104 | 105 | const keys = Array.isArray(keyboard) ? keyboard : [keyboard]; 106 | 107 | if (keys.indexOf(keyNames) > -1) { 108 | state.commands[name](); // 执行对应的命令的方法 AAAAAAA 109 | ev.stopPropagation(); 110 | ev.preventDefault(); 111 | } 112 | }) 113 | } 114 | 115 | const init = () => { 116 | window.addEventListener('keydown', onKeydown, true); 117 | return () => { 118 | window.removeEventListener('keydown', onKeydown, true) 119 | } 120 | } 121 | return { 122 | init 123 | } 124 | }); 125 | 126 | // 初始化注册命令(useRegistry)时的所有的 command 的 init 的方法 127 | const useInit = useCallback(() => { 128 | useState(() => { 129 | state.commandList.forEach(command => { 130 | command.current.init && state.destroyList.push(command.current.init()); 131 | }); 132 | state.destroyList.push(keyboardEvent.init()); 133 | }); 134 | 135 | // 注册内置的撤回命令(撤回命令执行的结果是不需要进入命令队列的) 136 | useRegistry({ 137 | name: 'undo', 138 | keyboard: 'ctrl+z', 139 | followQueue: false, // 标识不需要进入命令队列 140 | execute: () => { 141 | return { 142 | redo: () => { 143 | if (state.current === -1) return; 144 | const queueItem = state.queue[state.current]; 145 | if (queueItem) { 146 | queueItem.undo && queueItem.undo(); 147 | state.current--; 148 | } 149 | } 150 | } 151 | } 152 | }); 153 | 154 | // 注册内置的重做命令(重做命令执行结果是不需要进入命令队列的) 155 | useRegistry({ 156 | name: 'redo', 157 | keyboard: ['ctrl+y', 'ctrl+shift+z'], 158 | followQueue: false, 159 | execute: () => { 160 | return { 161 | redo: () => { 162 | const queueItem = state.queue[state.current + 1]; 163 | if (queueItem) { 164 | queueItem.redo(); 165 | state.current++; 166 | } 167 | } 168 | } 169 | } 170 | }); 171 | }, []); 172 | 173 | return { 174 | state, 175 | useInit, 176 | useRegistry 177 | } 178 | } -------------------------------------------------------------------------------- /src/packages/editor.command.tsx: -------------------------------------------------------------------------------- 1 | import deepcopy from "deepcopy"; 2 | import { useRef } from "react"; 3 | import { VisualEditorBlockData, VisualEditorValue } from "./editor.utils"; 4 | import { useCallbackRef } from "./hook/useCallbackRef"; 5 | import { useCommander } from "./plugin/command.plugin"; 6 | 7 | 8 | export function useVisualCommand({ 9 | focusData, 10 | value, 11 | updateBlocks, 12 | updateValue, 13 | dragend, 14 | dragstart 15 | }: { 16 | focusData: { 17 | focus: VisualEditorBlockData[], 18 | unFocus: VisualEditorBlockData[] 19 | }, 20 | value: VisualEditorValue, 21 | updateBlocks: (blocks: VisualEditorBlockData[]) => void, 22 | updateValue: (value: VisualEditorValue) => void, 23 | dragstart: { 24 | on: (cb: () => void) => void, 25 | off: (cb: () => void) => void 26 | }, 27 | dragend: { 28 | on: (cb: () => void) => void, 29 | off: (cb: () => void) => void 30 | } 31 | }) { 32 | const commander = useCommander(); 33 | 34 | // 注册一个删除命令操作 35 | commander.useRegistry({ 36 | name: 'delete', 37 | keyboard: ['delete', 'ctrl+d', 'backspace'], 38 | execute: () => { 39 | const data = { 40 | before: (() => deepcopy(value.blocks))(), 41 | after: (() => deepcopy(focusData.unFocus))() 42 | } 43 | return { 44 | redo: () => { // 重做 45 | updateBlocks(deepcopy(data.after)); 46 | }, 47 | undo: () => { // 撤销 48 | updateBlocks(deepcopy(data.before)); 49 | } 50 | } 51 | } 52 | }); 53 | 54 | // 注册一个清空命令操作 55 | commander.useRegistry({ 56 | name: 'clear', 57 | execute: () => { 58 | const data = { 59 | before: deepcopy(value.blocks), 60 | after: deepcopy([]), 61 | } 62 | return { 63 | redo: () => { 64 | updateBlocks(deepcopy(data.after)); 65 | }, 66 | undo: () => { 67 | updateBlocks(deepcopy(data.before)); 68 | }, 69 | } 70 | } 71 | }); 72 | 73 | { 74 | const dragData = useRef({ before: null as null | VisualEditorBlockData[] }); 75 | 76 | const handler = { // 拖拽开始或结束就会通过已经订阅的事件来触发这个 dragstart、dragend 函数,执行对应的函数逻辑 77 | dragstart: useCallbackRef(() => dragData.current.before = deepcopy(value.blocks)), 78 | dragend: useCallbackRef(() => commander.state.commands.drag()) 79 | } 80 | /** 81 | * 注册拖拽命令 82 | * 适用于如下三种情况: 83 | * 1. 从左侧菜单拖拽组件到容器画布; 84 | * 2. 在容器中拖拽组件调整位置; 85 | * 3. 拖动调整组件的高度和宽度。 86 | */ 87 | commander.useRegistry({ 88 | name: 'drag', 89 | init: () => { 90 | dragData.current = { before: null }; 91 | dragstart.on(handler.dragstart); 92 | dragend.on(handler.dragend); 93 | return () => { 94 | dragstart.off(handler.dragstart); 95 | dragend.off(handler.dragend); 96 | } 97 | }, 98 | execute: () => { 99 | const data = { 100 | before: deepcopy(dragData.current.before), 101 | after: deepcopy(value.blocks) 102 | }; 103 | return { 104 | redo: () => { 105 | updateBlocks(deepcopy(data.after)); 106 | }, 107 | undo: () => { 108 | updateBlocks(deepcopy(data.before) || []); 109 | } 110 | } 111 | } 112 | }); 113 | } 114 | 115 | // 注册置顶命令 116 | commander.useRegistry({ 117 | name: 'placeTop', 118 | keyboard: 'ctrl+up', 119 | execute: () => { 120 | 121 | const data = { 122 | before: (() => deepcopy(value.blocks))(), 123 | after: (() => deepcopy(() => { 124 | const { focus, unFocus } = focusData; 125 | // 计算出 focus 选中的最大的 zIndex 值,unFocus 未选中的最小的 zIndex 值,计算它们的差值就是当前元素置顶的 zIndex 值 126 | const maxUnFocusIndex = unFocus.reduce((prev, item) => { 127 | return Math.max(prev, item.zIndex); 128 | }, -Infinity); 129 | const minFocusIndex = focus.reduce((prev, item) => { 130 | return Math.min(prev, item.zIndex); 131 | }, Infinity); 132 | let dur = maxUnFocusIndex - minFocusIndex + 1; 133 | if (dur >= 0) { 134 | dur++; 135 | focus.forEach(block => block.zIndex = block.zIndex + dur); 136 | } 137 | return value.blocks; 138 | }))()() 139 | }; 140 | return { 141 | redo: () => updateBlocks(deepcopy(data.after) || []), 142 | undo: () => updateBlocks(deepcopy(data.before)) 143 | }; 144 | } 145 | }); 146 | 147 | // 注册全选快捷键命令 148 | commander.useRegistry({ 149 | name: 'selectAll', 150 | keyboard: ['ctrl+a'], 151 | followQueue: false, 152 | execute: () => { 153 | return { 154 | redo: () => { 155 | value.blocks.forEach(block => block.focus = true); 156 | updateBlocks(value.blocks); 157 | } 158 | } 159 | } 160 | }); 161 | 162 | // 注册置顶命令 163 | commander.useRegistry({ 164 | name: 'placeBottom', 165 | keyboard: 'ctrl+down', 166 | execute: () => { 167 | 168 | const data = { 169 | before: (() => deepcopy(value.blocks))(), 170 | after: (() => deepcopy(() => { 171 | const { focus, unFocus } = focusData; 172 | // 计算出 focus 选中的最大的 zIndex 值,unFocus 未选中的最小的 zIndex 值,计算它们的差值就是当前元素置顶的 zIndex 值 173 | const minUnFocusIndex = unFocus.reduce((prev, item) => { 174 | return Math.min(prev, item.zIndex); 175 | }, Infinity); 176 | const maxFocusIndex = focus.reduce((prev, item) => { 177 | return Math.max(prev, item.zIndex); 178 | }, -Infinity); 179 | const minFocusIndex = focus.reduce((prev, item) => { 180 | return Math.min(prev, item.zIndex); 181 | }, Infinity); 182 | let dur = maxFocusIndex - minUnFocusIndex + 1; 183 | if (dur >= 0) { 184 | dur++; 185 | focus.forEach(block => block.zIndex = block.zIndex - dur); 186 | if (minFocusIndex - dur < 0) { 187 | dur = dur - minFocusIndex; 188 | value.blocks.forEach(block => block.zIndex = block.zIndex + dur); 189 | } 190 | } 191 | return value.blocks; 192 | }))()() 193 | }; 194 | 195 | return { 196 | redo: () => updateBlocks(deepcopy(data.after)), 197 | undo: () => updateBlocks(deepcopy(data.before)) 198 | }; 199 | } 200 | }); 201 | 202 | // 注册导入数据时,更新数据命令 203 | commander.useRegistry({ 204 | name: 'updateValue', 205 | execute: (newModelValue: VisualEditorValue) => { 206 | const data = { 207 | before: deepcopy(value), 208 | after: deepcopy(newModelValue) 209 | }; 210 | return { 211 | redo: () => updateValue(data.after), 212 | undo: () => updateValue(data.before) 213 | } 214 | } 215 | }); 216 | 217 | // 注册导入节点数据时,更新节点数据命令 218 | commander.useRegistry({ 219 | name: 'updateBlock', 220 | execute: (newBlock: VisualEditorBlockData, oldBlock: VisualEditorBlockData) => { 221 | let blocks = deepcopy(value.blocks); 222 | const data = { 223 | before: blocks, 224 | after: (() => { 225 | blocks = [...blocks]; 226 | const index = value.blocks.indexOf(oldBlock); 227 | if (index > -1) { 228 | blocks.splice(index, 1, newBlock); 229 | } 230 | return deepcopy(blocks); 231 | })(), 232 | } 233 | return { 234 | redo: () => { 235 | updateBlocks(deepcopy(data.after)); 236 | }, 237 | undo: () => { 238 | updateBlocks(deepcopy(data.before)); 239 | }, 240 | } 241 | }, 242 | }) 243 | 244 | // 初始内置的命令 undo,redo 245 | commander.useInit(); // 在底部调用 246 | return { 247 | delete: () => commander.state.commands.delete(), 248 | clear: () => commander.state.commands.clear(), 249 | undo: () => commander.state.commands.undo(), 250 | redo: () => commander.state.commands.redo(), 251 | placeBottom: () => commander.state.commands.placeBottom(), 252 | placeTop: () => commander.state.commands.placeTop(), 253 | updateValue: (newModelValue: VisualEditorValue) => commander.state.commands.updateValue(newModelValue), 254 | updateBlock: (newModelValue: VisualEditorValue, oldModelValue: VisualEditorBlockData) => commander.state.commands.updateBlock(newModelValue, oldModelValue), 255 | } 256 | } -------------------------------------------------------------------------------- /src/asset/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/packages/VisualEditor.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef, useState } from 'react'; 2 | import { Button, notification, Tooltip } from 'antd'; 3 | import { MenuUnfoldOutlined } from '@ant-design/icons' 4 | 5 | import '../asset/font/iconfont.css'; 6 | import classModule from './style/VisualEditor.module.scss'; 7 | 8 | import { createVisualBlock, VisualEditorBlockData, VisualEditorComponent, VisualEditorConfig, VisualEditorValue } from './editor.utils'; 9 | import { VisualEditorBlock } from './EditorBlock'; 10 | import { useCallbackRef } from './hook/useCallbackRef'; 11 | import { useVisualCommand } from './editor.command'; 12 | import { createEvent } from './plugin/event'; 13 | import { $$dialog } from './service/dialog/$$dialog'; 14 | import { $$dropdown, DropdownOption } from './service/dropdown/$$dropdown'; 15 | 16 | export const VisualEditor: React.FC<{ 17 | value: VisualEditorValue, 18 | config: VisualEditorConfig, 19 | onChange: (val: VisualEditorValue) => void, // 数据有变化出发外部函数 20 | }> = (props) => { 21 | // 当前是否处于编辑状态 22 | const [editing, setEditing] = useState(true); // true 处于编辑状态 23 | const [preview, setPreview] = useState(false); // true 处于预览状态 24 | 25 | const innerMethods = { 26 | // 切换编辑和运行状态 27 | toggleEditing () { 28 | setEditing(!editing); 29 | }, 30 | // 切换编辑和预览状态 31 | togglePreview () { 32 | setPreview(!preview); 33 | }, 34 | } 35 | 36 | // 拖拽开始和结束的事件监听 37 | const [dragstart] = useState(() => createEvent()); 38 | const [dragend] = useState(() => createEvent()); 39 | 40 | // 当前选中 block 组件元素的索引 41 | const [selectIndex, setSelectIndex] = useState(-1); // 默认 -1 没选中 42 | const selectBlock = useMemo(() => props.value.blocks[selectIndex], [props.value.blocks, selectIndex]); 43 | 44 | // 对外暴露方法 45 | const methods = { 46 | /** 47 | * 更新 block 数据,触发视图重新渲染 48 | * @param blocks 49 | */ 50 | updateBlocks: (blocks: VisualEditorBlockData[]) => { 51 | props.onChange({ 52 | ...props.value, 53 | blocks: [...blocks] 54 | }) 55 | }, 56 | /** 57 | * 更新整个画布容器的数据(所有 block 数据) 58 | * @param value 59 | */ 60 | updateValue: (value: VisualEditorValue) => { 61 | props.onChange({...value}); 62 | }, 63 | /** 64 | * 查看(导出)数据 65 | * @param block 66 | */ 67 | showBlockData: (block: VisualEditorBlockData) => { 68 | $$dialog.textarea(JSON.stringify(block), {editReadonly: true, title: '导出的JSON数据'}); 69 | }, 70 | /** 71 | * 导入数据 72 | * @param block 73 | */ 74 | importBlockData: async (block: VisualEditorBlockData) => { 75 | const text = await $$dialog.textarea('', { title: '请输入需要导入的节点数据JSON字符串' }); 76 | try { 77 | const data = JSON.parse(text || ''); 78 | commander.updateBlock(data, block); 79 | } catch (e) { 80 | console.error(e) 81 | notification.open({ 82 | type: 'error', 83 | message: '导入失败!', 84 | description: '导入的数据格式不正常,请检查!' 85 | }) 86 | } 87 | }, 88 | /** 89 | * 清空选中的数据 90 | */ 91 | clearFocus: (external?: VisualEditorBlockData) => { 92 | let blocks = [...props.value.blocks]; 93 | if (!blocks.length) return; 94 | if (external) { 95 | blocks = blocks.filter(item => item !== external); 96 | } 97 | blocks.forEach(block => block.focus = false); 98 | methods.updateBlocks(props.value.blocks); 99 | }, 100 | } 101 | 102 | // 画布容器 DOM 103 | // 能够确保 containerRef 的存在这样书写 104 | const containerRef = useRef({} as HTMLDivElement); 105 | // 不能够确保 containerRef 的存在这样书写 106 | // const containerRef = useRef(null as null | HTMLDivElement); 107 | 108 | const containerStyles = useMemo(() => { 109 | return { 110 | width: `${props.value.container.width}px`, 111 | height: `${props.value.container.height}px`, 112 | } 113 | }, [props.value.container.height, props.value.container.width]); 114 | 115 | /** 116 | * 存储数据中选中状态和未选中状态的数据 117 | */ 118 | const focusData = useMemo(() => { 119 | const focus: VisualEditorBlockData[] = []; 120 | const unFocus: VisualEditorBlockData[] = []; 121 | 122 | props.value.blocks.forEach(block => { 123 | (block.focus ? focus : unFocus).push(block); 124 | }); 125 | 126 | return { 127 | focus, 128 | unFocus 129 | } 130 | }, [props.value.blocks]); 131 | 132 | //#region 左侧菜单拖拽到画布容器区域内 133 | const menuDraggier = (() => { 134 | 135 | const dragData = useRef({ 136 | dragComponent: null as null | VisualEditorComponent // 左侧组件列表去拖拽的当前组件 137 | }); 138 | 139 | const container = { 140 | dragenter: useCallbackRef((e: DragEvent) => { 141 | e.dataTransfer!.dropEffect = 'move'; 142 | }), 143 | dragover: useCallbackRef((e: DragEvent) => { 144 | e.preventDefault(); 145 | }), 146 | dragleave: useCallbackRef((e: DragEvent) => { 147 | e.dataTransfer!.dropEffect = 'none'; 148 | }), 149 | drop: useCallbackRef((e: DragEvent) => { 150 | // 在容器画布添加组件 151 | // console.log('add') 152 | 153 | methods.updateBlocks([ 154 | ...props.value.blocks, 155 | createVisualBlock({ 156 | top: e.offsetY, 157 | left: e.offsetX, 158 | component: dragData.current.dragComponent! 159 | }) 160 | ]); 161 | 162 | const t = setTimeout(() => { 163 | // 拖拽结束后,等页面渲染完毕,才执行,否则拖拽后就不会在页面正常显示 164 | dragend.emit(); // 触发事件 165 | clearTimeout(t); 166 | }); 167 | }), 168 | }; 169 | 170 | const block = { 171 | dragstart: useCallbackRef((e: React.DragEvent, dragComponent: VisualEditorComponent) => { 172 | 173 | containerRef.current.addEventListener('dragenter', container.dragenter); 174 | containerRef.current.addEventListener('dragover', container.dragover); 175 | containerRef.current.addEventListener('dragleave', container.dragleave); 176 | containerRef.current.addEventListener('drop', container.drop); 177 | 178 | dragData.current.dragComponent = dragComponent; 179 | 180 | dragstart.emit(); // 触发事件 181 | 182 | }), 183 | dragend: useCallbackRef((e: React.DragEvent) => { 184 | 185 | containerRef.current.removeEventListener('dragenter', container.dragenter); 186 | containerRef.current.removeEventListener('dragover', container.dragover); 187 | containerRef.current.removeEventListener('dragleave', container.dragleave); 188 | containerRef.current.removeEventListener('drop', container.drop); 189 | }) 190 | }; 191 | 192 | return block; 193 | })(); 194 | //#endregion 195 | 196 | //#region 画布容器中 block 组件选中 197 | const focusHandler = (() => { 198 | const mousedownBlock = (e: React.MouseEvent, block: VisualEditorBlockData, index: number) => { 199 | e.stopPropagation(); 200 | if (preview) return; 201 | e.preventDefault(); 202 | 203 | // 右键不作任何处理, 防止右键时还可以拖动 204 | if (e.button === 2) return; 205 | 206 | if (e.shiftKey) { 207 | // 如果摁住了shift键,如果此时没有选中的 block,就选中该 block,否则使该 block 的数据选中状态取反 208 | if (focusData.focus.length <= 1) { 209 | block.focus = true; 210 | } else { 211 | block.focus = !block.focus; 212 | } 213 | methods.updateBlocks(props.value.blocks); 214 | } else { 215 | // 如果点击的这个 block 没有被选中,才清空这个其他选中的 block,否则不做任何事情。放置拖拽多个 block,取消其他 block 的选中状态 216 | if (!block.focus) { 217 | block.focus = true; 218 | methods.clearFocus(block); 219 | } 220 | } 221 | 222 | setSelectIndex(block.focus ? index : -1); 223 | // 使用延时器保证,数据时渲染后的正确数据,否则有 BUG 224 | setTimeout(() => { 225 | blockDraggier.mousedown(e, block); 226 | }); 227 | }; 228 | const mousedownContainer = (e: React.MouseEvent) => { 229 | e.stopPropagation(); 230 | if (preview) return; 231 | e.preventDefault(); 232 | 233 | // 判断不是点击了 container 容器就返回 234 | if (e.target !== e.currentTarget) return; 235 | 236 | // console.log('点击了 Contanier'); 237 | if (!e.shiftKey) { 238 | // 点击空白出清空所有的选中的 block 239 | methods.clearFocus(); 240 | setSelectIndex(-1); 241 | } 242 | }; 243 | 244 | return { 245 | block: mousedownBlock, 246 | container: mousedownContainer 247 | } 248 | })(); 249 | //#endregion 250 | 251 | //#region 画布容器组件的拖拽 252 | const blockDraggier = (() => { 253 | 254 | const [mark, setMark] = useState({x: null as null | number, y: null as null | number}); 255 | 256 | // 存储拖拽时的数据 257 | const dragData = useRef({ 258 | startX: 0, // 鼠标拖拽开始的,鼠标的横坐标 259 | startY: 0, // 鼠标拖拽开始的,鼠标的纵坐标 260 | startLeft: 0, // 鼠标拖拽开始的,拖拽的 block 横坐标 261 | startTop: 0, // 鼠标拖拽开始的,拖拽的 block 纵坐标 262 | startPosArray: [] as { top: number, left: number }[], // 鼠标拖拽开始的, 所有选中的 block 元素的横纵坐标值 263 | 264 | shiftKey: false, // 当前是否按住了 shift 键 265 | moveX: 0, // 拖拽过程中的时候, 鼠标的 left 值 266 | moveY: 0, // 拖拽过程中的时候, 鼠标的 top 值 267 | containerBar: { 268 | startScrollTop: 0, // 拖拽开始的时候, scrollTop 值 269 | moveScrollTop: 0, // 拖拽过程中的时候, scrollTop 值 270 | }, 271 | 272 | dragging: false, // 当前是否属于拖拽状态 273 | markLines: { // 拖拽元素时,计算当前未选中的数据中,与拖拽元素之间参考辅助线的显示位置 274 | x: [] as {left: number, showLeft: number}[], 275 | y: [] as {top: number, showTop: number}[] 276 | } 277 | }); 278 | const moveHandler = useCallbackRef(() => { 279 | if (!dragData.current.dragging) { 280 | dragData.current.dragging = true; 281 | dragstart.emit(); // 拖拽过程中派发事件 282 | } 283 | 284 | let { 285 | startX, 286 | startY, 287 | startPosArray, 288 | moveX, 289 | moveY, 290 | containerBar, 291 | startLeft, 292 | startTop, 293 | markLines, 294 | shiftKey 295 | } = dragData.current; 296 | 297 | moveY = moveY + (containerBar.moveScrollTop - containerBar.startScrollTop); 298 | 299 | // 移动时, 同时按住 shift 键,只在一个方向移动 300 | if (shiftKey) { 301 | const n = 12; // 预定差值 302 | if (Math.abs(moveX - startX) > Math.abs(moveY - startY) + n) { 303 | moveY = startY; 304 | } else { 305 | moveX = startX; 306 | } 307 | } 308 | 309 | //#region 参考线处理 310 | const nowMark = { 311 | mark: { 312 | x: null as null | number, 313 | y: null as null | number 314 | }, 315 | top: startTop + moveY - startY, 316 | left: startLeft + moveX - startX 317 | }; 318 | 319 | for (let i = 0; i < markLines.y.length; i++) { 320 | const { top, showTop } = markLines.y[i]; 321 | if (Math.abs(nowMark.top - top) < 5) { 322 | moveY = top + startY - startTop; 323 | nowMark.mark.y = showTop; 324 | } 325 | } 326 | 327 | for (let i = 0; i < markLines.x.length; i++) { 328 | const { left, showLeft } = markLines.x[i]; 329 | if (Math.abs(nowMark.left - left) < 5) { 330 | moveX = left + startX - startLeft; 331 | nowMark.mark.x = showLeft; 332 | } 333 | } 334 | //#endregion 335 | 336 | const durX = moveX - startX; 337 | const durY = moveY - startY; 338 | 339 | focusData.focus.forEach((block, index) => { 340 | const { left, top } = startPosArray[index]; 341 | block.left = left + durX; 342 | block.top = top + durY; 343 | }); 344 | methods.updateBlocks(props.value.blocks); 345 | 346 | // 修正参考线的位置 347 | setMark(nowMark.mark); 348 | }); 349 | 350 | const scrollHandler = useCallbackRef((e: Event) => { 351 | dragData.current.containerBar.moveScrollTop = (e.target as HTMLDivElement).scrollTop; 352 | moveHandler(); 353 | }); 354 | 355 | const mousemove = useCallbackRef((e: MouseEvent) => { 356 | dragData.current.moveX = e.clientX; 357 | dragData.current.moveY = e.clientY; 358 | moveHandler(); 359 | }); 360 | const mouseup = useCallbackRef((e: MouseEvent) => { 361 | document.removeEventListener('mousemove', mousemove); 362 | document.removeEventListener('mouseup', mouseup); 363 | // 取消参考辅助线 364 | setMark({x: null, y: null}); 365 | 366 | if (dragData.current.dragging) { 367 | dragData.current.dragging = false; 368 | dragend.emit(); // 拖拽过程中,鼠标抬起后才派发事件 369 | } 370 | }); 371 | const mousedown = useCallbackRef((e: React.MouseEvent, block: VisualEditorBlockData) => { 372 | 373 | document.addEventListener('mousemove', mousemove); 374 | document.addEventListener('mouseup', mouseup); 375 | 376 | dragData.current = { 377 | startX: e.clientX, 378 | startY: e.clientY, 379 | startLeft: block.left, 380 | startTop: block.top, 381 | startPosArray: focusData.focus.map(({ top, left }) => ({ top, left })), 382 | moveX: e.clientX, 383 | moveY: e.clientY, 384 | shiftKey: e.shiftKey, 385 | containerBar: { 386 | startScrollTop: 0, 387 | moveScrollTop: 0, 388 | }, 389 | dragging: false, 390 | markLines: (() => { 391 | const x: {left: number, showLeft: number}[] = []; 392 | const y: {top: number, showTop: number}[] = []; 393 | // 参考线处理 TODO... 394 | 395 | // 拖动的与未选中的对齐 396 | const { unFocus } = focusData; 397 | unFocus.forEach(v => { 398 | // 水平方向,拖动的顶部对未拖动的顶部 399 | y.push({ top: v.top, showTop: v.top }); 400 | // 水平方向,拖动的中间对未拖动的中间 401 | y.push({ top: v.top + v.height / 2 - block.height / 2, showTop: v.top + v.height / 2 }); 402 | // 水平方向,拖动的底部对未拖动的底部 403 | y.push({ top: v.top + v.height - block.height, showTop: v.top + v.height}); 404 | // 水平方向,拖动的底部对未拖动的顶部 405 | y.push({ top: v.top - block.height, showTop: v.top }); 406 | // 水平方向,拖动的底部对未拖动的底部 407 | y.push({ top: v.top + v.height, showTop: v.top + v.height }); 408 | 409 | // 垂直方向,拖动的左侧对未拖动的左侧 410 | x.push({ left: v.left, showLeft: v.left }); 411 | // 垂直方向,拖动的中间对未拖动的中间 412 | x.push({ left: v.left + v.width / 2 - block.width / 2, showLeft: v.left + v.width / 2 }); 413 | // 垂直方向,拖动的右侧对未拖动的右侧 414 | x.push({ left: v.left + v.width - block.width, showLeft: v.left + v.width}); 415 | // 垂直方向,拖动的右侧对未拖动的左侧 416 | x.push({ left: v.left - block.width, showLeft: v.left }); 417 | // 垂直方向,拖动的左侧对未拖动的右侧 418 | x.push({ left: v.left + v.width, showLeft: v.left + v.width }); 419 | }); 420 | return { x, y } 421 | })() 422 | } 423 | }); 424 | 425 | return { 426 | mousedown, 427 | mark 428 | } 429 | })(); 430 | //#endregion 431 | 432 | // 命令管理对象 433 | const commander = useVisualCommand({ 434 | value: props.value, 435 | focusData, 436 | updateBlocks: methods.updateBlocks, 437 | dragstart, 438 | dragend, 439 | updateValue: methods.updateValue 440 | }); 441 | 442 | const handler = { 443 | onContextMenuBlock: (ev: React.MouseEvent, block: VisualEditorBlockData) => { 444 | ev.preventDefault(); 445 | ev.stopPropagation(); 446 | 447 | 448 | $$dropdown({ 449 | reference: ev.nativeEvent, 450 | render: () => { 451 | return (<> 452 | 453 | 454 | 455 | methods.showBlockData(block)}}/> 456 | methods.importBlockData(block)}}/> 457 | ) 458 | } 459 | }); 460 | } 461 | }; 462 | 463 | //#region 功能操作栏按钮组 464 | const buttons: { 465 | label: string | (() => string), 466 | icon: string | (() => string), 467 | tip?: string | (() => string), 468 | handler: () => void, 469 | }[] = [ 470 | { 471 | label: '撤销', 472 | icon: 'icon-back', 473 | handler: () => { 474 | // console.log('撤销') 475 | commander.undo(); 476 | }, 477 | tip: 'ctrl+z' 478 | }, 479 | { 480 | label: '重做', 481 | icon: 'icon-forward', 482 | handler: () => { 483 | // console.log('重做') 484 | commander.redo(); 485 | }, 486 | tip: 'ctrl+y, ctrl+shift+z' 487 | }, 488 | { 489 | label: '清空', 490 | icon: 'icon-reset', 491 | handler: () => { 492 | // console.log('清空') 493 | commander.clear(); 494 | } 495 | }, 496 | { 497 | label: '删除', 498 | icon: 'icon-delete', 499 | handler: () => { 500 | // console.log('删除') 501 | commander.delete(); 502 | }, 503 | tip: 'ctrl+d, backspace, delete' 504 | }, 505 | { 506 | label: '置顶', 507 | icon: 'icon-place-top', 508 | handler: () => { 509 | // console.log('置顶') 510 | commander.placeTop(); 511 | }, 512 | tip: 'ctrl+up' 513 | }, 514 | { 515 | label: '置底', 516 | icon: 'icon-place-bottom', 517 | handler: () => { 518 | // console.log('置底') 519 | commander.placeBottom(); 520 | 521 | }, 522 | tip: 'ctrl+down' 523 | }, 524 | { 525 | label: '导入', 526 | icon: 'icon-import', 527 | handler: async () => { 528 | console.log('导入'); 529 | const text = await $$dialog.textarea('', { title: '请输入导入的JSON字符串' }); 530 | try { 531 | const data = JSON.parse(text || ''); 532 | commander.updateValue(data); 533 | } catch (e) { 534 | console.error(e) 535 | notification.open({ 536 | type: 'error', 537 | message: '导入失败!', 538 | description: '导入的数据格式不正常,请检查!' 539 | }) 540 | } 541 | } 542 | }, 543 | { 544 | label: '导出', 545 | icon: 'icon-export', 546 | handler: () => { 547 | console.log('导出') 548 | $$dialog.textarea(JSON.stringify(props.value), {editReadonly: true, title: '导出的JSON数据'}); 549 | } 550 | }, 551 | { 552 | label: () => preview ? '编辑' : '预览', 553 | icon: () => preview ? 'icon-edit' : 'icon-browse', 554 | handler: () => { 555 | if (!preview && !editing) { 556 | methods.clearFocus(); 557 | } 558 | innerMethods.togglePreview(); 559 | }, 560 | }, 561 | { 562 | label: '关闭', 563 | icon: 'icon-close', 564 | handler: () => { 565 | if (!editing) { 566 | methods.clearFocus(); 567 | } 568 | innerMethods.toggleEditing(); 569 | } 570 | } 571 | ] 572 | //#endregion 573 | 574 | return (<> 575 | { 576 | editing ? ( 577 |
578 |
579 |
580 | 组件列表 581 |
582 | { 583 | props.config.componentList.map((component, index) => { 584 | return ( 585 |
menuDraggier.dragstart(e, component)} 590 | onDragEnd={menuDraggier.dragend} 591 | > 592 | {component.label} 593 |
594 | {component.preview()} 595 |
596 |
597 | ) 598 | }) 599 | } 600 |
601 |
602 | { 603 | buttons.map((btn, index) => { 604 | const label = typeof btn.label === "function" ? btn.label() : btn.label 605 | const icon = typeof btn.icon === "function" ? btn.icon() : btn.icon 606 | const content = (
607 | 608 | {label} 609 |
) 610 | return !btn.tip ? content : 611 | {content} 612 | 613 | }) 614 | } 615 |
616 |
operator
617 |
618 |
624 | { 625 | props.value.blocks.map((block, index) => { 626 | return focusHandler.block(e, block, index)} 633 | onContextMenu={e => handler.onContextMenuBlock(e, block)} 634 | /> 635 | }) 636 | } 637 | {/*参考辅助线 start*/} 638 | {blockDraggier.mark.x !== null &&
} 639 | {blockDraggier.mark.y !== null &&
} 640 | {/*参考辅助线 end*/} 641 |
642 |
643 |
644 | ) : ( 645 |
646 |
647 |
648 |
649 | { 650 | props.value.blocks.map((block, index) => { 651 | return 656 | }) 657 | } 658 |
659 |
660 |
661 | ) 662 | } 663 | ); 664 | }; -------------------------------------------------------------------------------- /doc/开发文档.md: -------------------------------------------------------------------------------- 1 | # Vite2 + React17 + Typescript4 + Ant Design 4 可视化拖拽页面编辑器 2 | 3 | > 随着大前端的不断发展,越来越解放开发的双手,感觉要失业啦(^_^),针对一些简单模板处理,可直接通过个拖拉拽放,就可简单实现一些不错的 UI 功能。那么 `可视化拖拽页面编辑器` 的出现也是顺应时代的发展而出现。 4 | 5 | 6 | 7 | ***最终效果*** 8 | 9 | ![最终效果](./最终效果.png) 10 | 11 | # 任务清单 12 | 13 | - [X] 主页面结构:左侧菜单栏可选组件列表、中间容器画布、右侧编辑组件定义的属性; 14 | - [X] 左侧菜单栏可选组件列表渲染; 15 | - [x] 从菜单栏拖拽组件到容器; 16 | - [x] 组件(Block)在容器的选中状态; 17 | - [x] 容器内组件可移动位置; 18 | - [X] 容器内的组件单选、多选、全选; 19 | - [X] 命令队列及对应的快捷键; 20 | - [X] 操作栏按钮: 21 | - [X] 撤销、重做 **重难点**; 22 | - [X] 删除、清空; 23 | - [X] 预览、关闭编辑模式; 24 | - [X] 置顶、置底; 25 | - [X] 导入、导出; 26 | - [X] 右键菜单; 27 | - [X] 拖拽参考线; 28 | - [ ] 组件可以拖动调整高度和宽度(height,width); 29 | - [ ] 组件可以设置预定好的属性(props); 30 | - [ ] 组件绑定值(model); 31 | - [ ] 设置组件标识(soltName),根据这个标识,定义某个组件的行为(函数触发)和插槽的实现(自定义视图); 32 | - [ ] 完善可选组件列表: 33 | - [ ] 输入框:双向绑定值,调整宽度; 34 | - [ ] 按钮:类型、文字、大小尺寸、拖拽调整宽高; 35 | - [ ] 图片:自定义图片地址、拖拽调整图片宽高 36 | - [ ] 下拉框:预定义选项值、双向绑定字段; 37 | 38 | 39 | # 一、项目搭建与页面布局 40 | 41 | 1. 项目所需依赖: 42 | 43 | ```json 44 | "dependencies": { 45 | "@ant-design/icons": "^4.5.0", 46 | "antd": "^4.15.0", 47 | "classnames": "^2.2.6", 48 | "deepcopy": "^2.1.0", 49 | "react": "^17.0.1", 50 | "react-color": "^2.19.3", 51 | "react-dom": "^17.0.1" 52 | }, 53 | "devDependencies": { 54 | "@types/classnames": "^2.2.11", 55 | "@types/node": "^14.14.37", 56 | "@types/react": "^17.0.2", 57 | "@types/react-color": "^3.0.4", 58 | "@types/react-dom": "^17.0.1", 59 | "@vitejs/plugin-react-refresh": "^1.3.1", 60 | "less": "^4.1.1", 61 | "sass": "^1.42.0", 62 | "typescript": "^4.1.5", 63 | "vite": "^2.0.1", 64 | "vite-plugin-babel-import": "^2.0.5", 65 | "vite-plugin-style-import": "^1.2.1" 66 | } 67 | ``` 68 | 69 | 2. vite 配置 70 | 71 | ```js 72 | const path = require('path'); 73 | import reactRefresh from '@vitejs/plugin-react-refresh'; 74 | import { defineConfig } from 'vite'; 75 | import styleImport from 'vite-plugin-style-import'; 76 | 77 | export default defineConfig({ 78 | plugins: [ 79 | reactRefresh(), 80 | styleImport({ 81 | libs: [ 82 | { 83 | libraryName: 'antd', 84 | esModule: true, 85 | resolveStyle: (name) => { 86 | return `antd/es/${name}/style`; 87 | }, 88 | } 89 | ] 90 | }), 91 | ], 92 | css: { 93 | preprocessorOptions: { 94 | less: { 95 | javascriptEnabled: true 96 | } 97 | } 98 | }, 99 | esbuild: { 100 | jsxInject: "import React from 'react'", // 为每个 tsx jsx 自动引入 React,不用手动引入了 101 | }, 102 | resolve: { 103 | alias: { 104 | "@": path.resolve(__dirname, "src"), 105 | "@assets": path.resolve(__dirname, "src/assets"), 106 | "@components": path.resolve(__dirname, "src/components") 107 | } 108 | }, 109 | server: { 110 | https: false, // 是否开启 https 111 | open: true, // 是否自动在浏览器打开 112 | port: 3000, // 端口号 113 | host: "0.0.0.0", 114 | hmr: { 115 | overlay: true, // 是否开启错误的阴影层 116 | } 117 | }, 118 | optimizeDeps: { 119 | include: [] // 第三方库 120 | }, 121 | build: { 122 | chunkSizeWarningLimit: 2000, 123 | terserOptions: { 124 | // 生产环境移除 console 125 | compress: { 126 | drop_console: true, 127 | drop_debugger: true, 128 | }, 129 | }, 130 | rollupOptions: { 131 | output:{ 132 | manualChunks: { // 分包 133 | react: ['react', 'react-dom'], 134 | antd: ['antd'] 135 | } 136 | } 137 | } 138 | } 139 | }) 140 | ``` 141 | 142 | 143 | 144 | ### 实现基本的左中右布局 145 | 146 | - 左侧是组件列表的菜单栏 147 | - 中间是画布容器和顶部的工具栏,用来编辑和预览页面 148 | - 右侧是对应画布容器中某个组件,对应显示的该组件的属性配置 149 | 150 | 这布局下相对简单就脑补一下下吧 ^_^ 151 | 152 | 153 | 154 | # 二、基本数据结构设计 155 | 156 | > 针对于一个画布容器与组件的关系,对应一个画布大小变化,和一个画布中对应多个组件,一个组件配置相关信息,对于拖拽大家一定想到,**定位**,如果优化,可以使用 CSS3 的属性 `transform` 的 `translateX`,` translateY`优化。这里暂时不做处理。 157 | 158 | - 定义数据结构 159 | - container:画布容器信息 160 | - blocks:画布容器中的组件信息 161 | - blocks中存放每个 block 组件信息,包含了组件的唯一标识、位置、宽高、状态等信息 162 | 163 | ```tsx 164 | /** 165 | * 容器中每个元素的的数据类型 166 | */ 167 | export interface VisualEditorBlock { 168 | componentKey: string, // component 对象的的 key 唯一标识 169 | top: number, // block 在容器中的 top 位置 170 | left: number, // block 在容器中的 left 位置 171 | width: number, // block 组件自身的宽度 172 | height: number, // block 组件自身的高度 173 | adjustPosition: boolean, // 添加组件到容器中时是否需要调整位置 174 | focus: boolean, // 组件是否是选中状态 175 | zIndex: number, // block 组件元素的 z-index style 属性 176 | hasReasize: boolean, // block 组件元素是否曾调整国大小 177 | props?: Record // block 组件元素右侧属性配置信息 178 | model?: Record // 组件元素右侧自定义配置属性信息(绑定值) 179 | slotName?: string // 组件标识 180 | } 181 | /** 182 | * 编辑器编辑的数据类型 183 | */ 184 | export interface VisualEditorValue { 185 | container: { // 画布容器 186 | height: number, 187 | width: number, 188 | }, 189 | blocks: VisualEditorBlock[] 190 | } 191 | ``` 192 | 193 | [代码传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/25575720ea2b1c13d6572ae8dad41ea1621fd5c5) 194 | 195 | 196 | # 三、左侧菜单栏可选组件列表渲染 197 | 198 | 1. 开发注册组件 199 | 200 | ```tsx 201 | // visual.config.tsx 202 | import { Button, Input } from "antd"; 203 | import { createVisualConfig } from "./editor.utils"; 204 | 205 | export const visualConfig = createVisualConfig(); 206 | 207 | visualConfig.registryComponent('text', { 208 | label: '文本', 209 | preview: () => 预览文本, 210 | render: () => 渲染文本 211 | }); 212 | 213 | visualConfig.registryComponent('button', { 214 | label: '按钮', 215 | preview: () => , 216 | render: () => 217 | }); 218 | 219 | visualConfig.registryComponent('input', { 220 | label: '输入框', 221 | preview: () => , 222 | render: () => 223 | }); 224 | ``` 225 | 226 | **注册组件的函数**: 227 | 228 | ```tsx 229 | /** 230 | * 创建编辑器的预设内容 231 | */ 232 | export function createVisualConfig() { 233 | // 用于 block 数据,通过 componentKey 找到 component 对象,使用 component 对象的 render 属性渲染内容到 container 容器里 234 | const componentMap: { [k: string]: VisualEditorComponent } = {}; 235 | // 用户在 menu 中预定义的组件列表 236 | const componentList: VisualEditorComponent[] = []; 237 | 238 | const registryComponent = (key: string, options: Omit) => { 239 | // key 是唯一的 240 | if (componentMap[key]) { 241 | const index = componentList.indexOf(componentMap[key]); 242 | componentList.splice(index, 1); 243 | } 244 | const newComponent = { 245 | key, 246 | ...options 247 | } 248 | 249 | componentList.push(newComponent); 250 | componentMap[key] = newComponent; 251 | } 252 | 253 | return { 254 | componentList, 255 | componentMap, 256 | registryComponent 257 | } 258 | } 259 | ``` 260 | 261 | 2. 渲染注册的组件 262 | 263 | ```tsx 264 | // VisualEditor.tsx 265 | 266 | // 代码省略..... 267 | 268 | export const VisualEditor: React.FC<{ 269 | value: VisualEditorValue, 270 | config: VisualEditorConfig 271 | }> = (props) => { 272 | 273 | return (<> 274 |
275 |
276 |
277 | 组件列表 278 |
279 | { 280 | props.config.componentList.map((component, index) => { 281 | return ( 282 |
283 | {component.label} 284 |
285 | {component.preview()} 286 |
287 |
288 | ) 289 | }) 290 | } 291 |
292 |
header
293 |
operator
294 |
body
295 |
296 | ); 297 | }; 298 | ``` 299 | 300 | ***效果*** 301 | 302 | ![左侧菜单栏列表渲染](./左侧菜单列表渲染.png) 303 | 304 | [代码传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/25575720ea2b1c13d6572ae8dad41ea1621fd5c5) 305 | 306 | 307 | # 四、block 组件画布容器区域渲染 308 | 309 | 1. block 组件 310 | 311 | ```tsx 312 | // EditorBlock.tsx 313 | export const VisualEditorBlock: React.FC<{ 314 | block: VisualEditorBlockData, 315 | config: VisualEditorConfig, 316 | editing: boolean 317 | }> = (props) => { 318 | const style = useMemo(() => { 319 | return { 320 | top: `${props.block.top}px`, 321 | left: `${props.block.left}px`, 322 | } 323 | }, [props.block.top, props.block.left]); 324 | 325 | const component = props.config.componentMap[props.block.componentKey]; 326 | 327 | let render: any; 328 | if (!!component) { 329 | render = component.render({} as any); 330 | } 331 | 332 | return (() => { 333 | const mask = props.editing ? 'mask': ''; 334 | return ( 335 |
336 | {render} 337 |
338 | ) 339 | })() 340 | } 341 | ``` 342 | 343 | 2. block 组件的渲染 344 | 345 | ```tsx 346 | // VisualEditor.tsx 347 | export const VisualEditor: React.FC<{ 348 | value: VisualEditorValue, 349 | config: VisualEditorConfig 350 | }> = (props) => { 351 | // 代码省略..... 352 | 353 | const containerStyles = useMemo(() => { 354 | return { 355 | width: `${props.value.container.width}px`, 356 | height: `${props.value.container.height}px`, 357 | } 358 | }, [props.value.container.height, props.value.container.width]); 359 | return (<> 360 | { 361 | editing ? ( 362 |
363 | {/* // 代码省略..... */} 364 |
header
365 |
operator
366 |
367 |
368 | { 369 | props.value.blocks.map((block, index) => { 370 | return 376 | }) 377 | } 378 |
379 |
380 |
381 | ) : ( 382 |
383 |
384 |
385 |
386 | { 387 | props.value.blocks.map((block, index) => { 388 | return 394 | }) 395 | } 396 |
397 |
398 |
399 | ) 400 | } 401 | ); 402 | }; 403 | ``` 404 | 405 | [代码传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/b3df70f8a0b324ace90e28899c9d8f03c3f9df40) 406 | 407 | ***效果图*** 408 | 409 | ![容器区域组件基础渲染](./容器区域组件基础渲染.png) 410 | 411 | # 五、左侧菜单组件拖拽到画布容器区域渲染 412 | 413 | > 监听 html5 拖拽函数 414 | 415 | 416 | 417 | ```tsx 418 | // VisualEditor.tsx 419 | 420 | export const VisualEditor: React.FC<{ 421 | value: VisualEditorValue, 422 | config: VisualEditorConfig, 423 | onChange: (val: VisualEditorValue) => void, // 数据有变化出发外部函数 424 | }> = (props) => { 425 | // 当前是否处于编辑状态 426 | const [editing, setEditing] = useState(true); 427 | const methods = { 428 | // 切换编辑和运行状态 429 | toggleEditing () { 430 | setEditing(!editing); 431 | }, 432 | /** 433 | * 更新 block 数据,触发视图重新渲染 434 | * @param blocks 435 | */ 436 | updateBlocks: (blocks: VisualEditorBlockData[]) => { 437 | props.onChange({ 438 | ...props.value, 439 | blocks: [...blocks] 440 | }) 441 | }, 442 | } 443 | 444 | // 画布容器 DOM 445 | const containerRef = useRef({} as HTMLDivElement); 446 | 447 | const containerStyles = useMemo(() => { 448 | return { 449 | width: `${props.value.container.width}px`, 450 | height: `${props.value.container.height}px`, 451 | } 452 | }, [props.value.container.height, props.value.container.width]); 453 | 454 | //#region 左侧菜单拖拽到画布容器区域内 455 | const menuDraggier = (() => { 456 | 457 | const dragData = useRef({ 458 | dragComponent: null as null | VisualEditorComponent // 左侧组件列表去拖拽的当前组件 459 | }); 460 | 461 | const container = { 462 | dragenter: useCallbackRef((e: DragEvent) => { 463 | e.dataTransfer!.dropEffect = 'move'; 464 | }), 465 | dragover: useCallbackRef((e: DragEvent) => { 466 | e.preventDefault(); 467 | }), 468 | dragleave: useCallbackRef((e: DragEvent) => { 469 | e.dataTransfer!.dropEffect = 'none'; 470 | }), 471 | drop: useCallbackRef((e: DragEvent) => { 472 | // 在容器画布添加组件 473 | console.log('add') 474 | 475 | methods.updateBlocks([ 476 | ...props.value.blocks, 477 | createVisualBlock({ 478 | top: e.offsetY, 479 | left: e.offsetX, 480 | component: dragData.current.dragComponent! 481 | }) 482 | ]); 483 | 484 | }), 485 | }; 486 | 487 | const block = { 488 | dragstart: useCallbackRef((e: React.DragEvent, dragComponent: VisualEditorComponent) => { 489 | 490 | containerRef.current.addEventListener('dragenter', container.dragenter); 491 | containerRef.current.addEventListener('dragover', container.dragover); 492 | containerRef.current.addEventListener('dragleave', container.dragleave); 493 | containerRef.current.addEventListener('drop', container.drop); 494 | 495 | dragData.current.dragComponent = dragComponent; 496 | 497 | }), 498 | dragend: useCallbackRef((e: React.DragEvent) => { 499 | 500 | containerRef.current.removeEventListener('dragenter', container.dragenter); 501 | containerRef.current.removeEventListener('dragover', container.dragover); 502 | containerRef.current.removeEventListener('dragleave', container.dragleave); 503 | containerRef.current.removeEventListener('drop', container.drop); 504 | }) 505 | }; 506 | 507 | return block; 508 | })(); 509 | //#endregion 510 | 511 | return (<> 512 | { 513 | editing ? ( 514 |
515 | {/* // 代码省略..... */} 516 |
header
517 |
operator
518 |
519 |
524 | { 525 | props.value.blocks.map((block, index) => { 526 | return 532 | }) 533 | } 534 |
535 |
536 |
537 | ) : ( 538 | // 代码省略..... 539 | ) 540 | } 541 | ); 542 | }; 543 | ``` 544 | 545 | [代码传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/a33d3799e3024b72fa213072a14928ae478dc63c) 546 | 547 | 548 | 549 | 550 | 551 | # 六、画布容器中组件选中 552 | 553 | > 是否选中,为每个元素组件绑定一个字段 focus 做判断处理,可以按住 `Shift` 选中多个,点击画布容器空白处,取消所有的选中状态。 554 | 555 | ```tsx 556 | // VisualEditor.tsx 557 | 558 | const methods = { 559 | /** 560 | * 更新 block 数据,触发视图重新渲染 561 | * @param blocks 562 | */ 563 | updateBlocks: (blocks: VisualEditorBlockData[]) => { 564 | props.onChange({ 565 | ...props.value, 566 | blocks: [...blocks] 567 | }) 568 | }, 569 | } 570 | //#region 画布容器中 block 组件选中 571 | const focusHandler = (() => { 572 | const mousedownBlock = (e: React.MouseEvent, block: VisualEditorBlockData, index: number) => { 573 | e.stopPropagation(); 574 | if (preview) return; 575 | e.preventDefault(); 576 | 577 | if (e.shiftKey) { 578 | // 如果摁住了shift键,如果此时没有选中的 block,就选中该 block,否则使该 block 的数据选中状态取反 579 | if (focusData.focus.length <= 1) { 580 | block.focus = true; 581 | } else { 582 | block.focus = !block.focus; 583 | } 584 | methods.updateBlocks(props.value.blocks); 585 | } else { 586 | // 如果点击的这个 block 没有被选中,才清空这个其他选中的 block,否则不做任何事情。放置拖拽多个 block,取消其他 block 的选中状态 587 | if (!block.focus) { 588 | block.focus = true; 589 | methods.clearFocus(block); 590 | } 591 | } 592 | 593 | setSelectIndex(block.focus ? index : -1); 594 | // 使用延时器保证,数据时渲染后的正确数据,否则有 BUG 595 | setTimeout(() => { 596 | blockDraggier.mousedown(e, block); 597 | }); 598 | }; 599 | const mousedownContainer = (e: React.MouseEvent) => { 600 | e.stopPropagation(); 601 | if (preview) return; 602 | e.preventDefault(); 603 | 604 | // 右键不作任何处理 605 | if (e.button === 1) return; 606 | // 判断不是点击了 container 容器就返回 607 | if (e.target !== e.currentTarget) return; 608 | 609 | // console.log('点击了 Contanier'); 610 | if (!e.shiftKey) { 611 | // 点击空白出清空所有的选中的 block 612 | methods.clearFocus(); 613 | setSelectIndex(-1); 614 | } 615 | }; 616 | 617 | return { 618 | block: mousedownBlock, 619 | container: mousedownContainer 620 | } 621 | })(); 622 | //#endregion 623 | ``` 624 | 625 | ***效果图*** 626 | ![画布容器中组件选中](./画布容器中组件选中.png) 627 | 628 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/539a99057735659c73bd34991d28591f5557b814) 629 | 630 | # 七、选中的组件进行拖拽 631 | 632 | > 多个选中也一起移动 633 | 634 | ```tsx 635 | // VisualEditor.tsx 636 | 637 | const methods = { 638 | /** 639 | * 更新 block 数据,触发视图重新渲染 640 | * @param blocks 641 | */ 642 | updateBlocks: (blocks: VisualEditorBlockData[]) => { 643 | props.onChange({ 644 | ...props.value, 645 | blocks: [...blocks] 646 | }) 647 | }, 648 | /** 649 | * 清空选中的数据 650 | */ 651 | clearFocus: (external?: VisualEditorBlockData) => { 652 | let blocks = [...props.value.blocks]; 653 | if (!blocks.length) return; 654 | if (external) { 655 | blocks = blocks.filter(item => item !== external); 656 | } 657 | blocks.forEach(block => block.focus = false); 658 | methods.updateBlocks(props.value.blocks); 659 | }, 660 | } 661 | 662 | //#region 画布容器组件的拖拽 663 | const blockDraggier = (() => { 664 | 665 | const [mark, setMark] = useState({x: null as null | number, y: null as null | number}); 666 | 667 | // 存储拖拽时的数据 668 | const dragData = useRef({ 669 | startX: 0, // 鼠标拖拽开始的,鼠标的横坐标 670 | startY: 0, // 鼠标拖拽开始的,鼠标的纵坐标 671 | startLeft: 0, // 鼠标拖拽开始的,拖拽的 block 横坐标 672 | startTop: 0, // 鼠标拖拽开始的,拖拽的 block 纵坐标 673 | startPosArray: [] as { top: number, left: number }[], // 鼠标拖拽开始的, 所有选中的 block 元素的横纵坐标值 674 | 675 | shiftKey: false, // 当前是否按住了 shift 键 676 | moveX: 0, // 拖拽过程中的时候, 鼠标的 left 值 677 | moveY: 0, // 拖拽过程中的时候, 鼠标的 top 值 678 | containerBar: { 679 | startScrollTop: 0, // 拖拽开始的时候, scrollTop 值 680 | moveScrollTop: 0, // 拖拽过程中的时候, scrollTop 值 681 | }, 682 | 683 | dragging: false, // 当前是否属于拖拽状态 684 | markLines: { // 拖拽元素时,计算当前未选中的数据中,与拖拽元素之间参考辅助线的显示位置 685 | x: [] as {left: number, showLeft: number}[], 686 | y: [] as {top: number, showTop: number}[] 687 | } 688 | }); 689 | const moveHandler = useCallbackRef(() => { 690 | if (!dragData.current.dragging) { 691 | dragData.current.dragging = true; 692 | } 693 | 694 | let { 695 | startX, 696 | startY, 697 | startPosArray, 698 | moveX, 699 | moveY, 700 | containerBar, 701 | startLeft, 702 | startTop, 703 | markLines, 704 | shiftKey 705 | } = dragData.current; 706 | 707 | moveY = moveY + (containerBar.moveScrollTop - containerBar.startScrollTop); 708 | 709 | // 移动时, 同时按住 shift 键,只在一个方向移动 710 | if (shiftKey) { 711 | const n = 12; // 预定差值 712 | if (Math.abs(moveX - startX) > Math.abs(moveY - startY) + n) { 713 | moveY = startY; 714 | } else { 715 | moveX = startX; 716 | } 717 | } 718 | 719 | const durX = moveX - startX; 720 | const durY = moveY - startY; 721 | 722 | focusData.focus.forEach((block, index) => { 723 | const { left, top } = startPosArray[index]; 724 | block.left = left + durX; 725 | block.top = top + durY; 726 | }); 727 | methods.updateBlocks(props.value.blocks); 728 | 729 | }); 730 | 731 | const scrollHandler = useCallbackRef((e: Event) => { 732 | dragData.current.containerBar.moveScrollTop = (e.target as HTMLDivElement).scrollTop; 733 | moveHandler(); 734 | }); 735 | 736 | const mousemove = useCallbackRef((e: MouseEvent) => { 737 | dragData.current.moveX = e.clientX; 738 | dragData.current.moveY = e.clientY; 739 | moveHandler(); 740 | }); 741 | const mouseup = useCallbackRef((e: MouseEvent) => { 742 | document.removeEventListener('mousemove', mousemove); 743 | document.removeEventListener('mouseup', mouseup); 744 | 745 | if (dragData.current.dragging) { 746 | dragData.current.dragging = false; 747 | } 748 | }); 749 | const mousedown = useCallbackRef((e: React.MouseEvent, block: VisualEditorBlockData) => { 750 | 751 | document.addEventListener('mousemove', mousemove); 752 | document.addEventListener('mouseup', mouseup); 753 | 754 | dragData.current = { 755 | startX: e.clientX, 756 | startY: e.clientY, 757 | startLeft: block.left, 758 | startTop: block.top, 759 | startPosArray: focusData.focus.map(({ top, left }) => ({ top, left })), 760 | moveX: e.clientX, 761 | moveY: e.clientY, 762 | shiftKey: e.shiftKey, 763 | containerBar: { 764 | startScrollTop: 0, 765 | moveScrollTop: 0, 766 | }, 767 | dragging: false, 768 | markLines: (() => { 769 | const x = [{ left: 0, showLeft: 0}]; 770 | const y = [{ top: 0, showTop: 0}]; 771 | return { x, y } 772 | })() 773 | } 774 | }); 775 | return { 776 | mousedown, 777 | mark 778 | } 779 | })(); 780 | //#endregion 781 | ``` 782 | 783 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/16eaacbbb1e9d682d8bfc972e4f5d45261870a09) 784 | 785 | # 八、顶部操作栏 786 | 787 | > 编辑的操作必要的一些删除,撤销,重做,导入,导出 788 | 789 | ```tsx 790 | VisualEditor.tsx 791 | 792 | //#region 功能操作栏按钮组 793 | const buttons: { 794 | label: string | (() => string), 795 | icon: string | (() => string), 796 | tip?: string | (() => string), 797 | handler: () => void, 798 | }[] = [ 799 | { 800 | label: '撤销', 801 | icon: 'icon-back', 802 | handler: () => { 803 | console.log('撤销') 804 | }, 805 | tip: 'ctrl+z' 806 | }, 807 | { 808 | label: '重做', 809 | icon: 'icon-forward', 810 | handler: () => { 811 | console.log('重做') 812 | }, 813 | tip: 'ctrl+y, ctrl+shift+z' 814 | }, 815 | { 816 | label: '导入', 817 | icon: 'icon-import', 818 | handler: async () => { 819 | console.log('导入') 820 | } 821 | }, 822 | { 823 | label: '导出', 824 | icon: 'icon-export', 825 | handler: () => { 826 | console.log('导出') 827 | } 828 | }, 829 | { 830 | label: '置顶', 831 | icon: 'icon-place-top', 832 | handler: () => { 833 | console.log('置顶') 834 | }, 835 | tip: 'ctrl+up' 836 | }, 837 | { 838 | label: '置底', 839 | icon: 'icon-place-bottom', 840 | handler: () => { 841 | console.log('置底') 842 | }, 843 | tip: 'ctrl+down' 844 | }, 845 | { 846 | label: '删除', 847 | icon: 'icon-delete', 848 | handler: () => { 849 | console.log('删除') 850 | }, tip: 'ctrl+d, backspace, delete' 851 | }, 852 | { 853 | label: '清空', 854 | icon: 'icon-reset', 855 | handler: () => { 856 | console.log('清空') 857 | } 858 | }, 859 | { 860 | label: () => preview ? '编辑' : '预览', 861 | icon: () => preview ? 'icon-edit' : 'icon-browse', 862 | handler: () => { 863 | if (!preview && !editing) { 864 | methods.clearFocus(); 865 | } 866 | innerMethods.togglePreview(); 867 | }, 868 | }, 869 | { 870 | label: '关闭', 871 | icon: 'icon-close', 872 | handler: () => { 873 | if (!editing) { 874 | methods.clearFocus(); 875 | } 876 | innerMethods.toggleEditing(); 877 | } 878 | } 879 | ] 880 | //#endregion 881 | 882 | // 省略代码...... 883 | 884 |
885 | { 886 | buttons.map((btn, index) => { 887 | const label = typeof btn.label === "function" ? btn.label() : btn.label 888 | const icon = typeof btn.icon === "function" ? btn.icon() : btn.icon 889 | const content = (
890 | 891 | {label} 892 |
) 893 | return !btn.tip ? content : 894 | {content} 895 | 896 | }) 897 | } 898 |
899 | ``` 900 | 901 | ***效果图*** 902 | ![顶部操作栏.png](./顶部操作栏.png) 903 | 904 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/8e41eb0d2600331d4ea43231dc0b3a27b080cc22) 905 | 906 | # 九、撤销,重做,删除、清空命令实现 907 | 908 | 909 | - 基础命令初始化 910 | 911 | ```ts 912 | // command.plugin.ts 913 | import { useCallback, useRef, useState } from "react"; 914 | 915 | // command 的 execute 执行完之后,需要返回 undo、redo。execute 执行后会立即返回 redo,后续撤销的时候会执行 undo,重做的时候会执行 redo 916 | interface CommandExecute { 917 | redo: () => void, // 默认执行,重做会调用 918 | undo?: () => void, // 撤销会调用 919 | } 920 | 921 | interface Command { 922 | name: string, // 命令的唯一标识 923 | execute: (...args: any[]) => CommandExecute, // 命令执行时候,所处理的内容 924 | keyboard?: string | string[], // 命令监听的快捷键 925 | followQueue?: boolean, // 命令执行之后,是否需要将命令执行得到的 undo,redo 存入命令队列(像全选、撤销、重做这中命令不需要存入命令队列的) 926 | init?: () => ((() => void) | undefined), // 命令初始化函数,如果返回的,则是一个销毁命令函数 927 | data?: any // 命令缓存所需的数据信息 928 | } 929 | 930 | export function useCommander() { 931 | 932 | const [state] = useState(() => ({ 933 | current: -1, // 当前命令队列中,最后执行的命令返回的 CommandExecute 对象 934 | queue: [] as CommandExecute[], // 命令队列容器 935 | commandList: [] as { current: Command }[], // 预定义命令的容器 936 | commands: {} as Record void>, // 通过 command name 执行 command 动作的一个包装对象 937 | destroyList: [] as ((() => void) | undefined)[] // 所有命令在组件销毁之前,需要执行的消除副作用的函数容器 938 | })); 939 | 940 | /** 941 | * 注册命令 942 | */ 943 | const useRegistry = useCallback((command: Command) => { 944 | const commandRef = useRef(command); 945 | commandRef.current = command; 946 | useState(() => { 947 | // 判断命令是否存在 948 | if (state.commands[command.name]) { 949 | const existIndex = state.commandList.findIndex(item => item.current.name === command.name); 950 | state.commandList.splice(existIndex, 1); 951 | } 952 | state.commandList.push(commandRef); 953 | // 对应命令的方法 AAAAAAA 954 | state.commands[command.name] = (...args: any[]) => { 955 | const { redo, undo } = commandRef.current.execute(...args); 956 | // 默认执行重做 957 | redo(); 958 | // 如果命令执行后,不需要进入命令队列,就直接结束 959 | if (commandRef.current.followQueue === false) { 960 | return; 961 | } 962 | // 否则,将命令队列中剩余的命令都删除,保留 current 及其之前的命令 963 | let { queue, current } = state; 964 | if (queue.length > 0) { 965 | queue = queue.slice(0, current + 1); 966 | state.queue = queue; 967 | } 968 | // 将命令队列中最后一个命令为i当前执行的命令 969 | queue.push({ undo, redo }); 970 | // 索引加 1, 指向队列中的最有一个命令 971 | state.current = current + 1; 972 | } 973 | /** 974 | * commands 结构类型 975 | * { 976 | * undo: () => {}, 977 | * redo: () => {}, 978 | * delete: () => {}, 979 | * clear: () => {}, 980 | * placeTop: () => {}, 981 | * placeBottom: () => {} 982 | * } 983 | */ 984 | 985 | }); 986 | }, []); 987 | 988 | // 初始化注册命令(useRegistry)时的所有的 command 的 init 的方法 989 | const useInit = useCallback(() => { 990 | useState(() => { 991 | state.commandList.forEach(command => { 992 | command.current.init && state.destroyList.push(command.current.init()); 993 | }); 994 | // state.destroyList.push(keyboardEvent.init()); 995 | }); 996 | 997 | // 注册内置的撤回命令(撤回命令执行的结果是不需要进入命令队列的) 998 | useRegistry({ 999 | name: 'undo', 1000 | keyboard: 'ctrl+z', 1001 | followQueue: false, // 标识不需要进入命令队列 1002 | execute: () => { 1003 | return { 1004 | redo: () => { 1005 | if (state.current === -1) return; 1006 | const queueItem = state.queue[state.current]; 1007 | if (queueItem) { 1008 | queueItem.undo && queueItem.undo(); 1009 | state.current--; 1010 | } 1011 | } 1012 | } 1013 | } 1014 | }); 1015 | 1016 | // 注册内置的重做命令(重做命令执行结果是不需要进入命令队列的) 1017 | useRegistry({ 1018 | name: 'redo', 1019 | keyboard: ['ctrl+y', 'ctrl+shift+z'], 1020 | followQueue: false, 1021 | execute: () => { 1022 | return { 1023 | redo: () => { 1024 | const queueItem = state.queue[state.current + 1]; 1025 | if (queueItem) { 1026 | queueItem.redo(); 1027 | state.current++; 1028 | } 1029 | } 1030 | } 1031 | } 1032 | }); 1033 | }, []); 1034 | 1035 | return { 1036 | state, 1037 | useInit, 1038 | useRegistry 1039 | } 1040 | } 1041 | ``` 1042 | 1043 | - 扩展命令注册并导出 1044 | 1045 | ```tsx 1046 | // editor.command.tsx 1047 | import deepcopy from "deepcopy"; 1048 | import { VisualEditorBlockData, VisualEditorValue } from "./editor.utils"; 1049 | import { useCommander } from "./plugin/command.plugin"; 1050 | 1051 | export function useVisualCommand({ 1052 | focusData, 1053 | value, 1054 | updateBlocks 1055 | }: { 1056 | focusData: { 1057 | focus: VisualEditorBlockData[], 1058 | unFocus: VisualEditorBlockData[] 1059 | }, 1060 | value: VisualEditorValue, 1061 | updateBlocks: (blocks: VisualEditorBlockData[]) => void, 1062 | }) { 1063 | const commander = useCommander(); 1064 | 1065 | // 注册一个删除命令操作 1066 | commander.useRegistry({ 1067 | name: 'delete', 1068 | keyboard: ['delete', 'ctrl+d', 'backspace'], 1069 | execute: () => { 1070 | const data = { 1071 | before: (() => deepcopy(value.blocks))(), 1072 | after: (() => deepcopy(focusData.unFocus))() 1073 | } 1074 | return { 1075 | redo: () => { // 重做 1076 | updateBlocks(deepcopy(data.after)); 1077 | }, 1078 | undo: () => { // 撤销 1079 | updateBlocks(deepcopy(data.before)); 1080 | } 1081 | } 1082 | } 1083 | }); 1084 | 1085 | // 注册一个清空命令操作 1086 | commander.useRegistry({ 1087 | name: 'clear', 1088 | execute: () => { 1089 | const data = { 1090 | before: deepcopy(value.blocks), 1091 | after: deepcopy([]), 1092 | } 1093 | return { 1094 | redo: () => { 1095 | updateBlocks(deepcopy(data.after)); 1096 | }, 1097 | undo: () => { 1098 | updateBlocks(deepcopy(data.before)); 1099 | }, 1100 | } 1101 | } 1102 | }) 1103 | 1104 | // 初始内置的命令 undo,redo 1105 | commander.useInit(); // 在底部调用 1106 | return { 1107 | delete: () => commander.state.commands.delete(), 1108 | clear: () => commander.state.commands.clear(), 1109 | undo: () => commander.state.commands.undo(), 1110 | redo: () => commander.state.commands.redo(), 1111 | } 1112 | } 1113 | ``` 1114 | 1115 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/c1e7102f40200eeda74fbc457491e7e76b5113fc) 1116 | 1117 | # 十、绑定键盘快捷键事件 1118 | 1119 | > 快捷键的组合处理函数 1120 | 1121 | ```ts 1122 | // command.plugin.ts 1123 | 1124 | // 快捷键 1125 | const [keyboardEvent] = useState(() => { 1126 | const onKeydown = (ev: KeyboardEvent) => { 1127 | // 对于容器是否在空白区域或时操作某个组件的命令区分操作,比如空白区域时全选或全中所有的组件组件,在操作某个输入框组件时,全选就只会选中输入框中的文字 1128 | if (document.activeElement !== document.body) { 1129 | return; 1130 | } 1131 | const { keyCode, shiftKey, altKey, ctrlKey, metaKey } = ev; 1132 | 1133 | let keyString: string[] = []; 1134 | 1135 | if (ctrlKey || metaKey) { 1136 | keyString.push('ctrl'); 1137 | } 1138 | if (shiftKey) { 1139 | keyString.push('shift'); 1140 | } 1141 | if (altKey) { 1142 | keyString.push('alt'); 1143 | } 1144 | keyString.push(KeyboardCode[keyCode]); 1145 | 1146 | // 快捷键格式 'ctrl+alt+s' 1147 | const keyNames = keyString.join('+'); 1148 | 1149 | state.commandList.forEach(({ current: { keyboard, name } }) => { 1150 | if (!keyboard) return; 1151 | 1152 | const keys = Array.isArray(keyboard) ? keyboard : [keyboard]; 1153 | 1154 | if (keys.indexOf(keyNames) > -1) { 1155 | state.commands[name](); // 执行对应的命令的方法 AAAAAAA 1156 | ev.stopPropagation(); 1157 | ev.preventDefault(); 1158 | } 1159 | }) 1160 | } 1161 | ``` 1162 | 1163 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/2e4d65ac7e696daadfb0cc0c7d0055b9e7d31d90) 1164 | 1165 | # 十一、注册拖拽事件命令到命令队列中去 1166 | 1167 | > 拖拽开始和结束都需要派发出事件(发布订阅的模式),在画布容器移动的过程中也需要派发事件,拖拽结束鼠标抬起也要派发结束事件。 1168 | 1169 | ```ts 1170 | { 1171 | const dragData = useRef({ before: null as null | VisualEditorBlockData[] }); 1172 | 1173 | const handler = { 1174 | // 拖拽开始或结束就会通过已经订阅的事件来触发这个 dragstart、dragend 函数,执行对应的函数逻辑 1175 | dragstart: useCallbackRef(() => dragData.current.before = deepcopy(value.blocks)), 1176 | dragend: useCallbackRef(() => commander.state.commands.drag()) 1177 | } 1178 | /** 1179 | * 注册拖拽命令 1180 | * 适用于如下三种情况: 1181 | * 1. 从左侧菜单拖拽组件到容器画布; 1182 | * 2. 在容器中拖拽组件调整位置; 1183 | * 3. 拖动调整组件的高度和宽度。 1184 | */ 1185 | commander.useRegistry({ 1186 | name: 'drag', 1187 | init: () => { 1188 | dragData.current = { before: null }; 1189 | dragstart.on(handler.dragstart); 1190 | dragend.on(handler.dragend); 1191 | return () => { 1192 | dragstart.off(handler.dragstart); 1193 | dragend.off(handler.dragend); 1194 | } 1195 | }, 1196 | execute: () => { 1197 | const data = { 1198 | before: deepcopy(dragData.current.before), 1199 | after: deepcopy(value.blocks) 1200 | }; 1201 | return { 1202 | redo: () => { 1203 | updateBlocks(deepcopy(data.after)); 1204 | }, 1205 | undo: () => { 1206 | updateBlocks(deepcopy(data.before) || []); 1207 | } 1208 | } 1209 | } 1210 | }); 1211 | } 1212 | ``` 1213 | 1214 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/5e28b64f19b8fd11ca1ad442eaef876258624500) 1215 | 1216 | 1217 | 1218 | # 十二、顶部操作栏的置顶、置底 1219 | 1220 | 1221 | 1222 | ```ts 1223 | // VisualEditor.command.tsx 1224 | 1225 | // 注册置顶命令 1226 | commander.useRegistry({ 1227 | name: 'placeTop', 1228 | keyboard: 'ctrl+up', 1229 | execute: () => { 1230 | const data = { 1231 | before: (() => deepcopy(value.blocks))(), 1232 | after: (() => deepcopy(() => { 1233 | const { focus, unFocus } = focusData; 1234 | // 计算出 focus 选中的最大的 zIndex 值,unFocus 未选中的最小的 zIndex 值,计算它们的差值就是当前元素置顶的 zIndex 值 1235 | const maxUnFocusIndex = unFocus.reduce((prev, item) => { 1236 | return Math.max(prev, item.zIndex); 1237 | }, -Infinity); 1238 | const minFocusIndex = focus.reduce((prev, item) => { 1239 | return Math.min(prev, item.zIndex); 1240 | }, Infinity); 1241 | let dur = maxUnFocusIndex - minFocusIndex + 1; 1242 | if (dur >= 0) { 1243 | dur++; 1244 | focus.forEach(block => block.zIndex = block.zIndex + dur); 1245 | } 1246 | return value.blocks; 1247 | }))()() 1248 | }; 1249 | return { 1250 | redo: () => updateBlocks(deepcopy(data.after) || []), 1251 | undo: () => updateBlocks(deepcopy(data.before)) 1252 | }; 1253 | } 1254 | }); 1255 | 1256 | // 注册置顶命令 1257 | commander.useRegistry({ 1258 | name: 'placeBottom', 1259 | keyboard: 'ctrl+down', 1260 | execute: () => { 1261 | 1262 | const data = { 1263 | before: (() => deepcopy(value.blocks))(), 1264 | after: (() => deepcopy(() => { 1265 | // 这的置顶算法需要优化一下 1266 | const { focus, unFocus } = focusData; 1267 | // 计算出 focus 选中的最大的 zIndex 值,unFocus 未选中的最小的 zIndex 值,计算它们的差值就是当前元素置顶的 zIndex 值 1268 | const minUnFocusIndex = unFocus.reduce((prev, item) => { 1269 | return Math.min(prev, item.zIndex); 1270 | }, Infinity); 1271 | const maxFocusIndex = focus.reduce((prev, item) => { 1272 | return Math.max(prev, item.zIndex); 1273 | }, -Infinity); 1274 | const minFocusIndex = focus.reduce((prev, item) => { 1275 | return Math.min(prev, item.zIndex); 1276 | }, Infinity); 1277 | let dur = maxFocusIndex - minUnFocusIndex + 1; 1278 | if (dur >= 0) { 1279 | dur++; 1280 | focus.forEach(block => block.zIndex = block.zIndex - dur); 1281 | if (minFocusIndex - dur < 0) { 1282 | dur = dur - minFocusIndex; 1283 | value.blocks.forEach(block => block.zIndex = block.zIndex + dur); 1284 | } 1285 | } 1286 | return value.blocks; 1287 | }))()() 1288 | }; 1289 | 1290 | return { 1291 | redo: () => updateBlocks(deepcopy(data.after)), 1292 | undo: () => updateBlocks(deepcopy(data.before)) 1293 | }; 1294 | } 1295 | }); 1296 | ``` 1297 | 1298 | 1299 | 1300 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/e33501344a0ffc7d3fc5971823dfbbdd25c1a280) 1301 | 1302 | 1303 | 1304 | # 十三、注册全选快捷键命令 1305 | 1306 | ```ts 1307 | // VisualEditor.command.tsx 1308 | 1309 | // 注册全选快捷键命令 1310 | commander.useRegistry({ 1311 | name: 'selectAll', 1312 | keyboard: ['ctrl+a'], 1313 | followQueue: false, 1314 | execute: () => { 1315 | return { 1316 | redo: () => { 1317 | value.blocks.forEach(block => block.focus = true); 1318 | updateBlocks(value.blocks); 1319 | } 1320 | } 1321 | } 1322 | }); 1323 | ``` 1324 | 1325 | 1326 | 1327 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/363abf243b27cd27be17a1a9986dcf2d80e3f828) 1328 | 1329 | 1330 | 1331 | # 十四、操作栏导入、导出 1332 | 1333 | 1334 | 1335 | - 注册导入更新命令 1336 | 1337 | ```ts 1338 | // VisualEditor.command.tsx 1339 | 1340 | // 注册导入数据时,更新数据命令 1341 | commander.useRegistry({ 1342 | name: 'updateValue', 1343 | execute: (newModelValue: VisualEditorValue) => { 1344 | const data = { 1345 | before: deepcopy(value), 1346 | after: deepcopy(newModelValue) 1347 | }; 1348 | return { 1349 | redo: () => updateValue(data.after), 1350 | undo: () => updateValue(data.before) 1351 | } 1352 | } 1353 | }); 1354 | ``` 1355 | 1356 | - 导入、导出弹框 1357 | 1358 | ```tsx 1359 | // $$dialog.tsx 1360 | 1361 | export enum DialogEdit { 1362 | input = 'input', 1363 | textarea = 'textarea', 1364 | } 1365 | 1366 | interface DialogOption { 1367 | title?: string, 1368 | message?: string | (() => any), 1369 | confirmButton?: boolean 1370 | cancelButton?: boolean 1371 | editType?: DialogEdit, 1372 | editValue?: string, 1373 | editReadonly?: boolean 1374 | width?: string, 1375 | onConfirm?: (editValue?: string) => void, 1376 | onCancel?: () => void, 1377 | } 1378 | 1379 | interface DialogInstance { 1380 | show: (option?: DialogOption) => void, 1381 | close: () => void, 1382 | } 1383 | 1384 | const DialogComponent: React.FC<{ option?: DialogOption, onRef?: (ins: DialogInstance) => void }> = (props) => { 1385 | 1386 | let [option, setOption] = useState(props.option || {}); 1387 | const [showFlag, setShowFlag] = useState(false); 1388 | const [editValue, setEditValue] = useState(option ? option.editValue : ''); 1389 | 1390 | const methods = { 1391 | show: (option?: DialogOption) => { 1392 | setOption(deepcopy(option || {})); 1393 | setEditValue(!option ? '' : (option.editValue || '')); 1394 | setShowFlag(true); 1395 | }, 1396 | close: () => setShowFlag(false), 1397 | } 1398 | 1399 | props.onRef && props.onRef(methods); 1400 | 1401 | const handler = { 1402 | onConfirm: () => { 1403 | option.onConfirm && option.onConfirm(editValue) 1404 | methods.close() 1405 | }, 1406 | onCancel: () => { 1407 | option.onCancel && option.onCancel() 1408 | methods.close() 1409 | }, 1410 | }; 1411 | 1412 | const inputProps = { 1413 | value: editValue, 1414 | onChange: (e: React.ChangeEvent) => setEditValue(e.target.value), 1415 | readOnly: option.editReadonly === true, 1416 | }; 1417 | 1418 | return ( 1419 | 1427 | {option.cancelButton && } 1428 | {option.confirmButton && } 1429 | 1430 | ) : null} 1431 | > 1432 | {option.message} 1433 | {option.editType === DialogEdit.input && ( 1434 | 1435 | )} 1436 | {option.editType === DialogEdit.textarea && ( 1437 | 1438 | )} 1439 | 1440 | ) 1441 | 1442 | } 1443 | 1444 | const getInstance = (() => { 1445 | let ins: null | DialogInstance = null; 1446 | return (option?: DialogOption) => { 1447 | if (!ins) { 1448 | const el = document.createElement('div'); 1449 | document.body.appendChild(el); 1450 | ReactDOM.render( ins = val} />, el); 1451 | } 1452 | return ins!; 1453 | } 1454 | })(); 1455 | 1456 | const DialogService = (option?: DialogOption) => { 1457 | const ins = getInstance(option); 1458 | ins.show(option); 1459 | } 1460 | 1461 | export const $$dialog = Object.assign(DialogService, { 1462 | textarea: (val?: string, option?: DialogOption) => { 1463 | const dfd = defer(); 1464 | option = option || {}; 1465 | option.editType = DialogEdit.textarea; 1466 | option.editValue = val; 1467 | if (option.editReadonly !== true) { 1468 | option.confirmButton = true; 1469 | option.cancelButton = true; 1470 | option.onConfirm = dfd.resolve; 1471 | } 1472 | DialogService(option); 1473 | return dfd.promise; 1474 | }, 1475 | input: (val?: string, option?: DialogOption) => { 1476 | // TODO 1477 | console.log(val, option) 1478 | }, 1479 | }) 1480 | ``` 1481 | 1482 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/411da79dbbfa918fc79708c210c3fa5cc696f29a) 1483 | 1484 | 1485 | 1486 | # 十五、右键菜单 1487 | 1488 | - 菜单实现 1489 | 1490 | ```tsx 1491 | // src/packages/service/dropdown/$$dropdown.tsx 1492 | 1493 | import ReactDOM from "react-dom"; 1494 | import { MouseEvent, useContext, useEffect, useMemo, useRef, useState } from 'react'; 1495 | import './$$dropdown.scss'; 1496 | import { useCallbackRef } from '../../hook/useCallbackRef'; 1497 | 1498 | type Reference = { x: number, y: number } | MouseEvent | HTMLElement; 1499 | type Render = JSX.Element | JSX.Element[] | React.ReactFragment; 1500 | 1501 | interface DropdownOption { 1502 | reference: Reference, 1503 | render: () => Render, 1504 | } 1505 | 1506 | const DropdownContext = React.createContext<{onClick: () => void}>({} as any); 1507 | 1508 | const DropdownComponent: React.FC<{ 1509 | option: DropdownOption, 1510 | onRef: (ins: { show: (opt: DropdownOption) => void, close: () => void }) => void 1511 | }> = (props) => { 1512 | 1513 | const elRef = useRef({} as HTMLDivElement); 1514 | 1515 | const [option, setOption] = useState(props.option); 1516 | const [showFlag, setShowFlag] = useState(false); 1517 | 1518 | const styles = useMemo(() => { 1519 | let x = 0, y = 0; 1520 | const reference = option.reference 1521 | if ('target' in reference) { 1522 | x = reference.clientX - 20; 1523 | y = reference.clientY - 20; 1524 | } else if ('addEventListener' in reference) { 1525 | const { top, left, height } = reference.getBoundingClientRect(); 1526 | x = left; 1527 | y = top + height; 1528 | } else { 1529 | x = reference.x; 1530 | y = reference.y; 1531 | } 1532 | return { 1533 | left: `${x + 20}px`, 1534 | top: `${y + 20}px`, 1535 | display: showFlag ? 'inline-block' : 'none' 1536 | } 1537 | }, [option?.reference, showFlag]); 1538 | 1539 | const methods = { 1540 | show: (opt: DropdownOption) => { 1541 | setOption(opt); 1542 | setShowFlag(true); 1543 | }, 1544 | close: () => { 1545 | setShowFlag(false); 1546 | } 1547 | }; 1548 | 1549 | const handler = { 1550 | onClickBody: useCallbackRef((ev: MouseEvent) => { 1551 | if (elRef.current.contains(ev.target as Node)) { 1552 | /*点击了dropdown content*/ 1553 | return; 1554 | } else { 1555 | methods.close(); 1556 | } 1557 | }), 1558 | onClickDropItem: useCallbackRef(() => { 1559 | methods.close(); 1560 | }) 1561 | }; 1562 | 1563 | props.onRef!(methods); 1564 | 1565 | useEffect(() => { 1566 | document.body.addEventListener('click', handler.onClickBody as any); 1567 | return () => { 1568 | document.body.removeEventListener('click', handler.onClickBody as any); 1569 | } 1570 | }, []) 1571 | 1572 | return (<> 1573 | 1574 |
1575 | {option?.render()} 1576 |
1577 |
1578 | ); 1579 | } 1580 | 1581 | export const DropdownOption: React.FC<{ 1582 | onClick?: (ev: React.MouseEvent) => void 1583 | icon?: string, 1584 | label?: string 1585 | }> = (props) => { 1586 | 1587 | const dropdown = useContext(DropdownContext); 1588 | const handler = { 1589 | onClick: (e: React.MouseEvent) => { 1590 | dropdown.onClick(); 1591 | props.onClick && props.onClick(e); 1592 | } 1593 | }; 1594 | 1595 | return (
1596 | {props.icon && } 1597 | {props.label && {props.label}} 1598 | {/* {props.children} 这个时元素默认的标签内的内容*/} 1599 |
); 1600 | } 1601 | 1602 | export const $$dropdown = (() => { 1603 | 1604 | let ins: any; 1605 | return (options: DropdownOption) => { 1606 | if (!ins) { 1607 | const el = document.createElement('div'); 1608 | document.body.appendChild(el); 1609 | ReactDOM.render( ins = val} />, el); 1610 | } 1611 | 1612 | ins.show(options); 1613 | } 1614 | })(); 1615 | 1616 | 1617 | ``` 1618 | 1619 | 1620 | 1621 | - 注册事件监听并显示 1622 | 1623 | ```tsx 1624 | // src/packages/VisualEditor.tsx 1625 | 1626 | const handler = { 1627 | onContextMenuBlock: (ev: React.MouseEvent, block: VisualEditorBlockData) => { 1628 | ev.preventDefault(); 1629 | ev.stopPropagation(); 1630 | 1631 | 1632 | $$dropdown({ 1633 | reference: ev.nativeEvent, 1634 | render: () => { 1635 | return (<> 1636 | 1637 | 1638 | 1639 | methods.showBlockData(block)}}/> 1640 | methods.importBlockData(block)}}/> 1641 | ) 1642 | } 1643 | }); 1644 | } 1645 | }; 1646 | ``` 1647 | 1648 | [传送门](https://github.com/lyios8859-1/react-app-editor-project/commit/6ef3c7eb6a2c65ce7d3661cd592b12c6bddbcc03) 1649 | 1650 | ***效果图*** 1651 | 1652 | ![右键菜单](./右键菜单.png) 1653 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@ant-design/colors@^6.0.0": 6 | version "6.0.0" 7 | resolved "https://registry.npm.taobao.org/@ant-design/colors/download/@ant-design/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298" 8 | integrity sha1-m5NmJXz/zEfbQrnQIDu1ksE8Apg= 9 | dependencies: 10 | "@ctrl/tinycolor" "^3.4.0" 11 | 12 | "@ant-design/icons-svg@^4.0.0": 13 | version "4.1.0" 14 | resolved "https://registry.nlark.com/@ant-design/icons-svg/download/@ant-design/icons-svg-4.1.0.tgz#480b025f4b20ef7fe8f47d4a4846e4fee84ea06c" 15 | integrity sha1-SAsCX0sg73/o9H1KSEbk/uhOoGw= 16 | 17 | "@ant-design/icons@^4.5.0", "@ant-design/icons@^4.6.3": 18 | version "4.6.4" 19 | resolved "https://registry.nlark.com/@ant-design/icons/download/@ant-design/icons-4.6.4.tgz#21b037dbb90ee1bb7c632cca057006e57d992fd9" 20 | integrity sha1-IbA327kO4bt8YyzKBXAG5X2ZL9k= 21 | dependencies: 22 | "@ant-design/colors" "^6.0.0" 23 | "@ant-design/icons-svg" "^4.0.0" 24 | "@babel/runtime" "^7.11.2" 25 | classnames "^2.2.6" 26 | rc-util "^5.9.4" 27 | 28 | "@ant-design/react-slick@~0.28.1": 29 | version "0.28.4" 30 | resolved "https://registry.nlark.com/@ant-design/react-slick/download/@ant-design/react-slick-0.28.4.tgz?cache=0&sync_timestamp=1629256314194&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40ant-design%2Freact-slick%2Fdownload%2F%40ant-design%2Freact-slick-0.28.4.tgz#8b296b87ad7c7ae877f2a527b81b7eebd9dd29a9" 31 | integrity sha1-iylrh618euh38qUnuBt+69ndKak= 32 | dependencies: 33 | "@babel/runtime" "^7.10.4" 34 | classnames "^2.2.5" 35 | json2mq "^0.2.0" 36 | lodash "^4.17.21" 37 | resize-observer-polyfill "^1.5.0" 38 | 39 | "@babel/code-frame@^7.14.5": 40 | version "7.14.5" 41 | resolved "https://registry.nlark.com/@babel/code-frame/download/@babel/code-frame-7.14.5.tgz?cache=0&sync_timestamp=1623280394200&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" 42 | integrity sha1-I7CNdA6D9JxeWZRfvxtD6Au/Tts= 43 | dependencies: 44 | "@babel/highlight" "^7.14.5" 45 | 46 | "@babel/compat-data@^7.15.0": 47 | version "7.15.0" 48 | resolved "https://registry.nlark.com/@babel/compat-data/download/@babel/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" 49 | integrity sha1-Lbr4uFM0eWyvuw9Xk6kKL8AQsXY= 50 | 51 | "@babel/core@^7.14.8": 52 | version "7.15.5" 53 | resolved "https://registry.nlark.com/@babel/core/download/@babel/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9" 54 | integrity sha1-+O2aznMHIlRGCfkMm7SRYtw79bk= 55 | dependencies: 56 | "@babel/code-frame" "^7.14.5" 57 | "@babel/generator" "^7.15.4" 58 | "@babel/helper-compilation-targets" "^7.15.4" 59 | "@babel/helper-module-transforms" "^7.15.4" 60 | "@babel/helpers" "^7.15.4" 61 | "@babel/parser" "^7.15.5" 62 | "@babel/template" "^7.15.4" 63 | "@babel/traverse" "^7.15.4" 64 | "@babel/types" "^7.15.4" 65 | convert-source-map "^1.7.0" 66 | debug "^4.1.0" 67 | gensync "^1.0.0-beta.2" 68 | json5 "^2.1.2" 69 | semver "^6.3.0" 70 | source-map "^0.5.0" 71 | 72 | "@babel/generator@^7.12.11", "@babel/generator@^7.15.4": 73 | version "7.15.4" 74 | resolved "https://registry.nlark.com/@babel/generator/download/@babel/generator-7.15.4.tgz?cache=0&sync_timestamp=1630618918440&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fgenerator%2Fdownload%2F%40babel%2Fgenerator-7.15.4.tgz#85acb159a267ca6324f9793986991ee2022a05b0" 75 | integrity sha1-hayxWaJnymMk+Xk5hpke4gIqBbA= 76 | dependencies: 77 | "@babel/types" "^7.15.4" 78 | jsesc "^2.5.1" 79 | source-map "^0.5.0" 80 | 81 | "@babel/helper-compilation-targets@^7.15.4": 82 | version "7.15.4" 83 | resolved "https://registry.nlark.com/@babel/helper-compilation-targets/download/@babel/helper-compilation-targets-7.15.4.tgz?cache=0&sync_timestamp=1630618788550&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-compilation-targets%2Fdownload%2F%40babel%2Fhelper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" 84 | integrity sha1-z22U8w++/BORI+J91rAvZa7tt7k= 85 | dependencies: 86 | "@babel/compat-data" "^7.15.0" 87 | "@babel/helper-validator-option" "^7.14.5" 88 | browserslist "^4.16.6" 89 | semver "^6.3.0" 90 | 91 | "@babel/helper-function-name@^7.15.4": 92 | version "7.15.4" 93 | resolved "https://registry.nlark.com/@babel/helper-function-name/download/@babel/helper-function-name-7.15.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-function-name%2Fdownload%2F%40babel%2Fhelper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" 94 | integrity sha1-hFdE2vxDgaSl+2r6bD02+Yp4frw= 95 | dependencies: 96 | "@babel/helper-get-function-arity" "^7.15.4" 97 | "@babel/template" "^7.15.4" 98 | "@babel/types" "^7.15.4" 99 | 100 | "@babel/helper-get-function-arity@^7.15.4": 101 | version "7.15.4" 102 | resolved "https://registry.nlark.com/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.15.4.tgz?cache=0&sync_timestamp=1630618916983&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-get-function-arity%2Fdownload%2F%40babel%2Fhelper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" 103 | integrity sha1-CYgYk0oTf854tTaj4BWGS+Hih5s= 104 | dependencies: 105 | "@babel/types" "^7.15.4" 106 | 107 | "@babel/helper-hoist-variables@^7.15.4": 108 | version "7.15.4" 109 | resolved "https://registry.nlark.com/@babel/helper-hoist-variables/download/@babel/helper-hoist-variables-7.15.4.tgz?cache=0&sync_timestamp=1630618919536&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-hoist-variables%2Fdownload%2F%40babel%2Fhelper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" 110 | integrity sha1-CZk6MlnA6Rj5nRBCYd/fwDPxeN8= 111 | dependencies: 112 | "@babel/types" "^7.15.4" 113 | 114 | "@babel/helper-member-expression-to-functions@^7.15.4": 115 | version "7.15.4" 116 | resolved "https://registry.nlark.com/@babel/helper-member-expression-to-functions/download/@babel/helper-member-expression-to-functions-7.15.4.tgz?cache=0&sync_timestamp=1630618921004&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-member-expression-to-functions%2Fdownload%2F%40babel%2Fhelper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" 117 | integrity sha1-v9NNybupgkpGWLAxfsL9VxpR5u8= 118 | dependencies: 119 | "@babel/types" "^7.15.4" 120 | 121 | "@babel/helper-module-imports@^7.12.5", "@babel/helper-module-imports@^7.15.4": 122 | version "7.15.4" 123 | resolved "https://registry.nlark.com/@babel/helper-module-imports/download/@babel/helper-module-imports-7.15.4.tgz?cache=0&sync_timestamp=1630619202866&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-module-imports%2Fdownload%2F%40babel%2Fhelper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" 124 | integrity sha1-4YAH0jBjLeoZtHhTuYRHbntOED8= 125 | dependencies: 126 | "@babel/types" "^7.15.4" 127 | 128 | "@babel/helper-module-transforms@^7.15.4": 129 | version "7.15.7" 130 | resolved "https://registry.nlark.com/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.15.7.tgz?cache=0&sync_timestamp=1631920857520&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-module-transforms%2Fdownload%2F%40babel%2Fhelper-module-transforms-7.15.7.tgz#7da80c8cbc1f02655d83f8b79d25866afe50d226" 131 | integrity sha1-fagMjLwfAmVdg/i3nSWGav5Q0iY= 132 | dependencies: 133 | "@babel/helper-module-imports" "^7.15.4" 134 | "@babel/helper-replace-supers" "^7.15.4" 135 | "@babel/helper-simple-access" "^7.15.4" 136 | "@babel/helper-split-export-declaration" "^7.15.4" 137 | "@babel/helper-validator-identifier" "^7.15.7" 138 | "@babel/template" "^7.15.4" 139 | "@babel/traverse" "^7.15.4" 140 | "@babel/types" "^7.15.6" 141 | 142 | "@babel/helper-optimise-call-expression@^7.15.4": 143 | version "7.15.4" 144 | resolved "https://registry.nlark.com/@babel/helper-optimise-call-expression/download/@babel/helper-optimise-call-expression-7.15.4.tgz?cache=0&sync_timestamp=1630619283276&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-optimise-call-expression%2Fdownload%2F%40babel%2Fhelper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" 145 | integrity sha1-8xClEho7nMUtmrGRIr1ymCLe4XE= 146 | dependencies: 147 | "@babel/types" "^7.15.4" 148 | 149 | "@babel/helper-plugin-utils@^7.14.5": 150 | version "7.14.5" 151 | resolved "https://registry.nlark.com/@babel/helper-plugin-utils/download/@babel/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" 152 | integrity sha1-WsgizpfuxGdBq3ClF5ceRDpwxak= 153 | 154 | "@babel/helper-replace-supers@^7.15.4": 155 | version "7.15.4" 156 | resolved "https://registry.nlark.com/@babel/helper-replace-supers/download/@babel/helper-replace-supers-7.15.4.tgz?cache=0&sync_timestamp=1630618924259&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-replace-supers%2Fdownload%2F%40babel%2Fhelper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" 157 | integrity sha1-UqirJrqRjH9t7ihiiwcHGse3NHo= 158 | dependencies: 159 | "@babel/helper-member-expression-to-functions" "^7.15.4" 160 | "@babel/helper-optimise-call-expression" "^7.15.4" 161 | "@babel/traverse" "^7.15.4" 162 | "@babel/types" "^7.15.4" 163 | 164 | "@babel/helper-simple-access@^7.15.4": 165 | version "7.15.4" 166 | resolved "https://registry.nlark.com/@babel/helper-simple-access/download/@babel/helper-simple-access-7.15.4.tgz?cache=0&sync_timestamp=1630619204668&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-simple-access%2Fdownload%2F%40babel%2Fhelper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" 167 | integrity sha1-rDaJBavx3o6XgUNLY12PhnS8wTs= 168 | dependencies: 169 | "@babel/types" "^7.15.4" 170 | 171 | "@babel/helper-split-export-declaration@^7.15.4": 172 | version "7.15.4" 173 | resolved "https://registry.nlark.com/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.15.4.tgz?cache=0&sync_timestamp=1630618922438&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-split-export-declaration%2Fdownload%2F%40babel%2Fhelper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" 174 | integrity sha1-rsq5Lc2+9qEKo7YqsgSwhfd24lc= 175 | dependencies: 176 | "@babel/types" "^7.15.4" 177 | 178 | "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": 179 | version "7.15.7" 180 | resolved "https://registry.nlark.com/@babel/helper-validator-identifier/download/@babel/helper-validator-identifier-7.15.7.tgz?cache=0&sync_timestamp=1631920857390&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhelper-validator-identifier%2Fdownload%2F%40babel%2Fhelper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" 181 | integrity sha1-Ig35k7/pBKSmsCq08zhaXr9uI4k= 182 | 183 | "@babel/helper-validator-option@^7.14.5": 184 | version "7.14.5" 185 | resolved "https://registry.nlark.com/@babel/helper-validator-option/download/@babel/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" 186 | integrity sha1-bnKh//GNXfy4eOHmLxoCHEty1aM= 187 | 188 | "@babel/helpers@^7.15.4": 189 | version "7.15.4" 190 | resolved "https://registry.nlark.com/@babel/helpers/download/@babel/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" 191 | integrity sha1-X0DwIFCjAnEho89I1JfAXFVer0M= 192 | dependencies: 193 | "@babel/template" "^7.15.4" 194 | "@babel/traverse" "^7.15.4" 195 | "@babel/types" "^7.15.4" 196 | 197 | "@babel/highlight@^7.14.5": 198 | version "7.14.5" 199 | resolved "https://registry.nlark.com/@babel/highlight/download/@babel/highlight-7.14.5.tgz?cache=0&sync_timestamp=1623280393681&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fhighlight%2Fdownload%2F%40babel%2Fhighlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" 200 | integrity sha1-aGGlLwOWZAUAH2qlNKAaJNmejNk= 201 | dependencies: 202 | "@babel/helper-validator-identifier" "^7.14.5" 203 | chalk "^2.0.0" 204 | js-tokens "^4.0.0" 205 | 206 | "@babel/parser@^7.12.11", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5": 207 | version "7.15.7" 208 | resolved "https://registry.nlark.com/@babel/parser/download/@babel/parser-7.15.7.tgz?cache=0&sync_timestamp=1631920857646&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae" 209 | integrity sha1-DD7UousHsWXfqFs8xFxyczTE7a4= 210 | 211 | "@babel/plugin-transform-react-jsx-self@^7.14.5": 212 | version "7.14.9" 213 | resolved "https://registry.nlark.com/@babel/plugin-transform-react-jsx-self/download/@babel/plugin-transform-react-jsx-self-7.14.9.tgz?cache=0&sync_timestamp=1627804694133&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fplugin-transform-react-jsx-self%2Fdownload%2F%40babel%2Fplugin-transform-react-jsx-self-7.14.9.tgz#33041e665453391eb6ee54a2ecf3ba1d46bd30f4" 214 | integrity sha1-MwQeZlRTOR627lSi7PO6HUa9MPQ= 215 | dependencies: 216 | "@babel/helper-plugin-utils" "^7.14.5" 217 | 218 | "@babel/plugin-transform-react-jsx-source@^7.14.5": 219 | version "7.14.5" 220 | resolved "https://registry.nlark.com/@babel/plugin-transform-react-jsx-source/download/@babel/plugin-transform-react-jsx-source-7.14.5.tgz#79f728e60e6dbd31a2b860b0bf6c9765918acf1d" 221 | integrity sha1-efco5g5tvTGiuGCwv2yXZZGKzx0= 222 | dependencies: 223 | "@babel/helper-plugin-utils" "^7.14.5" 224 | 225 | "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": 226 | version "7.15.4" 227 | resolved "https://registry.nlark.com/@babel/runtime/download/@babel/runtime-7.15.4.tgz?cache=0&sync_timestamp=1630618785994&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" 228 | integrity sha1-/RfRa/34eObdAtGXU6OfqKjZyEo= 229 | dependencies: 230 | regenerator-runtime "^0.13.4" 231 | 232 | "@babel/template@^7.15.4": 233 | version "7.15.4" 234 | resolved "https://registry.nlark.com/@babel/template/download/@babel/template-7.15.4.tgz?cache=0&sync_timestamp=1630618922172&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Ftemplate%2Fdownload%2F%40babel%2Ftemplate-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" 235 | integrity sha1-UYmNNdzz+qZwxO5q/P1RfuE58ZQ= 236 | dependencies: 237 | "@babel/code-frame" "^7.14.5" 238 | "@babel/parser" "^7.15.4" 239 | "@babel/types" "^7.15.4" 240 | 241 | "@babel/traverse@^7.12.12", "@babel/traverse@^7.15.4": 242 | version "7.15.4" 243 | resolved "https://registry.nlark.com/@babel/traverse/download/@babel/traverse-7.15.4.tgz?cache=0&sync_timestamp=1630618923983&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Ftraverse%2Fdownload%2F%40babel%2Ftraverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" 244 | integrity sha1-/4UQNnoUS/v/VS2eGOKPPiiJwi0= 245 | dependencies: 246 | "@babel/code-frame" "^7.14.5" 247 | "@babel/generator" "^7.15.4" 248 | "@babel/helper-function-name" "^7.15.4" 249 | "@babel/helper-hoist-variables" "^7.15.4" 250 | "@babel/helper-split-export-declaration" "^7.15.4" 251 | "@babel/parser" "^7.15.4" 252 | "@babel/types" "^7.15.4" 253 | debug "^4.1.0" 254 | globals "^11.1.0" 255 | 256 | "@babel/types@^7.12.12", "@babel/types@^7.15.4", "@babel/types@^7.15.6": 257 | version "7.15.6" 258 | resolved "https://registry.nlark.com/@babel/types/download/@babel/types-7.15.6.tgz?cache=0&sync_timestamp=1631216248664&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" 259 | integrity sha1-mavcSCGLKIHAWN0KerBbmcm+dY8= 260 | dependencies: 261 | "@babel/helper-validator-identifier" "^7.14.9" 262 | to-fast-properties "^2.0.0" 263 | 264 | "@ctrl/tinycolor@^3.4.0": 265 | version "3.4.0" 266 | resolved "https://registry.npm.taobao.org/@ctrl/tinycolor/download/@ctrl/tinycolor-3.4.0.tgz?cache=0&sync_timestamp=1612895880147&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40ctrl%2Ftinycolor%2Fdownload%2F%40ctrl%2Ftinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" 267 | integrity sha1-w8WuVDyJfKqcKmhjC+01W+X5mQ8= 268 | 269 | "@icons/material@^0.2.4": 270 | version "0.2.4" 271 | resolved "https://registry.npm.taobao.org/@icons/material/download/@icons/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" 272 | integrity sha1-6QyfcXaLNzbnbX3WeD/Gwq+oi8g= 273 | 274 | "@rollup/pluginutils@^4.1.1": 275 | version "4.1.1" 276 | resolved "https://registry.nlark.com/@rollup/pluginutils/download/@rollup/pluginutils-4.1.1.tgz?cache=0&sync_timestamp=1626393703548&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40rollup%2Fpluginutils%2Fdownload%2F%40rollup%2Fpluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" 277 | integrity sha1-HU2obdTt7RVlalfZM/2iuaCNR+w= 278 | dependencies: 279 | estree-walker "^2.0.1" 280 | picomatch "^2.2.2" 281 | 282 | "@types/classnames@^2.2.11": 283 | version "2.3.1" 284 | resolved "https://registry.nlark.com/@types/classnames/download/@types/classnames-2.3.1.tgz?cache=0&sync_timestamp=1618929797687&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fclassnames%2Fdownload%2F%40types%2Fclassnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" 285 | integrity sha1-PCRnqg8ak/HwIeO5vPk4vV39wN0= 286 | dependencies: 287 | classnames "*" 288 | 289 | "@types/node@^14.14.37": 290 | version "14.17.18" 291 | resolved "https://registry.nlark.com/@types/node/download/@types/node-14.17.18.tgz?cache=0&sync_timestamp=1632251307629&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.17.18.tgz#0198489a751005f71217744aa966cd1f29447c81" 292 | integrity sha1-AZhImnUQBfcSF3RKqWbNHylEfIE= 293 | 294 | "@types/prop-types@*": 295 | version "15.7.4" 296 | resolved "https://registry.nlark.com/@types/prop-types/download/@types/prop-types-15.7.4.tgz?cache=0&sync_timestamp=1629708737049&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fprop-types%2Fdownload%2F%40types%2Fprop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" 297 | integrity sha1-/PcgXCXf95Xuea8eMNosl5CAjxE= 298 | 299 | "@types/react-color@^3.0.4": 300 | version "3.0.5" 301 | resolved "https://registry.nlark.com/@types/react-color/download/@types/react-color-3.0.5.tgz?cache=0&sync_timestamp=1629708935609&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Freact-color%2Fdownload%2F%40types%2Freact-color-3.0.5.tgz#b8bdf8df7085bd1577658fb37d9a18d7ba3963bb" 302 | integrity sha1-uL3433CFvRV3ZY+zfZoY17o5Y7s= 303 | dependencies: 304 | "@types/react" "*" 305 | "@types/reactcss" "*" 306 | 307 | "@types/react-dom@^17.0.1": 308 | version "17.0.9" 309 | resolved "https://registry.nlark.com/@types/react-dom/download/@types/react-dom-17.0.9.tgz?cache=0&sync_timestamp=1629708935856&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Freact-dom%2Fdownload%2F%40types%2Freact-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" 310 | integrity sha1-RBqYHanXvhFwQuGm/T2sSzD1Wt0= 311 | dependencies: 312 | "@types/react" "*" 313 | 314 | "@types/react@*", "@types/react@^17.0.2": 315 | version "17.0.24" 316 | resolved "https://registry.nlark.com/@types/react/download/@types/react-17.0.24.tgz#7e1b3f78d0fc53782543f9bce6d949959a5880bd" 317 | integrity sha1-fhs/eND8U3glQ/m85tlJlZpYgL0= 318 | dependencies: 319 | "@types/prop-types" "*" 320 | "@types/scheduler" "*" 321 | csstype "^3.0.2" 322 | 323 | "@types/reactcss@*": 324 | version "1.2.4" 325 | resolved "https://registry.nlark.com/@types/reactcss/download/@types/reactcss-1.2.4.tgz?cache=0&sync_timestamp=1629709158829&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Freactcss%2Fdownload%2F%40types%2Freactcss-1.2.4.tgz#66c5f6afe123ffa1a50dbe724aa1fe68eb9fab00" 326 | integrity sha1-ZsX2r+Ej/6GlDb5ySqH+aOufqwA= 327 | dependencies: 328 | "@types/react" "*" 329 | 330 | "@types/scheduler@*": 331 | version "0.16.2" 332 | resolved "https://registry.nlark.com/@types/scheduler/download/@types/scheduler-0.16.2.tgz?cache=0&sync_timestamp=1629709152024&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fscheduler%2Fdownload%2F%40types%2Fscheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" 333 | integrity sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk= 334 | 335 | "@vitejs/plugin-react-refresh@^1.3.1": 336 | version "1.3.6" 337 | resolved "https://registry.nlark.com/@vitejs/plugin-react-refresh/download/@vitejs/plugin-react-refresh-1.3.6.tgz#19818392db01e81746cfeb04e096ab3010e79fe3" 338 | integrity sha1-GYGDktsB6BdGz+sE4JarMBDnn+M= 339 | dependencies: 340 | "@babel/core" "^7.14.8" 341 | "@babel/plugin-transform-react-jsx-self" "^7.14.5" 342 | "@babel/plugin-transform-react-jsx-source" "^7.14.5" 343 | "@rollup/pluginutils" "^4.1.1" 344 | react-refresh "^0.10.0" 345 | 346 | ansi-styles@^3.2.1: 347 | version "3.2.1" 348 | resolved "https://registry.nlark.com/ansi-styles/download/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 349 | integrity sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0= 350 | dependencies: 351 | color-convert "^1.9.0" 352 | 353 | antd@^4.15.0: 354 | version "4.16.13" 355 | resolved "https://registry.nlark.com/antd/download/antd-4.16.13.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fantd%2Fdownload%2Fantd-4.16.13.tgz#e9b9b4a590db28747aae1cab98981649a35880af" 356 | integrity sha1-6bm0pZDbKHR6rhyrmJgWSaNYgK8= 357 | dependencies: 358 | "@ant-design/colors" "^6.0.0" 359 | "@ant-design/icons" "^4.6.3" 360 | "@ant-design/react-slick" "~0.28.1" 361 | "@babel/runtime" "^7.12.5" 362 | array-tree-filter "^2.1.0" 363 | classnames "^2.2.6" 364 | copy-to-clipboard "^3.2.0" 365 | lodash "^4.17.21" 366 | moment "^2.25.3" 367 | rc-cascader "~1.4.0" 368 | rc-checkbox "~2.3.0" 369 | rc-collapse "~3.1.0" 370 | rc-dialog "~8.6.0" 371 | rc-drawer "~4.3.0" 372 | rc-dropdown "~3.2.0" 373 | rc-field-form "~1.20.0" 374 | rc-image "~5.2.5" 375 | rc-input-number "~7.1.0" 376 | rc-mentions "~1.6.1" 377 | rc-menu "~9.0.12" 378 | rc-motion "^2.4.0" 379 | rc-notification "~4.5.7" 380 | rc-pagination "~3.1.9" 381 | rc-picker "~2.5.10" 382 | rc-progress "~3.1.0" 383 | rc-rate "~2.9.0" 384 | rc-resize-observer "^1.0.0" 385 | rc-select "~12.1.6" 386 | rc-slider "~9.7.1" 387 | rc-steps "~4.1.0" 388 | rc-switch "~3.2.0" 389 | rc-table "~7.15.1" 390 | rc-tabs "~11.10.0" 391 | rc-textarea "~0.3.0" 392 | rc-tooltip "~5.1.1" 393 | rc-tree "~4.2.1" 394 | rc-tree-select "~4.3.0" 395 | rc-trigger "^5.2.10" 396 | rc-upload "~4.3.0" 397 | rc-util "^5.13.1" 398 | scroll-into-view-if-needed "^2.2.25" 399 | 400 | anymatch@~3.1.2: 401 | version "3.1.2" 402 | resolved "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 403 | integrity sha1-wFV8CWrzLxBhmPT04qODU343hxY= 404 | dependencies: 405 | normalize-path "^3.0.0" 406 | picomatch "^2.0.4" 407 | 408 | array-tree-filter@^2.1.0: 409 | version "2.1.0" 410 | resolved "https://registry.nlark.com/array-tree-filter/download/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" 411 | integrity sha1-hzrAD+yDdJ8lWsjdCDgUtPYykZA= 412 | 413 | async-validator@^3.0.3: 414 | version "3.5.2" 415 | resolved "https://registry.nlark.com/async-validator/download/async-validator-3.5.2.tgz?cache=0&sync_timestamp=1630393256517&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fasync-validator%2Fdownload%2Fasync-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500" 416 | integrity sha1-aOhmqWgk6LJpT/eoMcGiXETV5QA= 417 | 418 | binary-extensions@^2.0.0: 419 | version "2.2.0" 420 | resolved "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.2.0.tgz?cache=0&sync_timestamp=1610299308660&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 421 | integrity sha1-dfUC7q+f/eQvyYgpZFvk6na9ni0= 422 | 423 | braces@~3.0.2: 424 | version "3.0.2" 425 | resolved "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 426 | integrity sha1-NFThpGLujVmeI23zNs2epPiv4Qc= 427 | dependencies: 428 | fill-range "^7.0.1" 429 | 430 | browserslist@^4.16.6: 431 | version "4.17.0" 432 | resolved "https://registry.nlark.com/browserslist/download/browserslist-4.17.0.tgz?cache=0&sync_timestamp=1630836439306&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.17.0.tgz#1fcd81ec75b41d6d4994fb0831b92ac18c01649c" 433 | integrity sha1-H82B7HW0HW1JlPsIMbkqwYwBZJw= 434 | dependencies: 435 | caniuse-lite "^1.0.30001254" 436 | colorette "^1.3.0" 437 | electron-to-chromium "^1.3.830" 438 | escalade "^3.1.1" 439 | node-releases "^1.1.75" 440 | 441 | camel-case@^4.1.2: 442 | version "4.1.2" 443 | resolved "https://registry.npm.taobao.org/camel-case/download/camel-case-4.1.2.tgz?cache=0&sync_timestamp=1606867297052&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamel-case%2Fdownload%2Fcamel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" 444 | integrity sha1-lygHKpVPgFIoIlpt7qazhGHhvVo= 445 | dependencies: 446 | pascal-case "^3.1.2" 447 | tslib "^2.0.3" 448 | 449 | caniuse-lite@^1.0.30001254: 450 | version "1.0.30001259" 451 | resolved "https://registry.nlark.com/caniuse-lite/download/caniuse-lite-1.0.30001259.tgz?cache=0&sync_timestamp=1632203439345&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001259.tgz#ae21691d3da9c4be6144403ac40f71d9f6efd790" 452 | integrity sha1-riFpHT2pxL5hREA6xA9x2fbv15A= 453 | 454 | capital-case@^1.0.4: 455 | version "1.0.4" 456 | resolved "https://registry.npm.taobao.org/capital-case/download/capital-case-1.0.4.tgz?cache=0&sync_timestamp=1606867325844&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcapital-case%2Fdownload%2Fcapital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" 457 | integrity sha1-nRMCkjU8kkn2sA+lhSvuOKcX5mk= 458 | dependencies: 459 | no-case "^3.0.4" 460 | tslib "^2.0.3" 461 | upper-case-first "^2.0.2" 462 | 463 | chalk@^2.0.0: 464 | version "2.4.2" 465 | resolved "https://registry.nlark.com/chalk/download/chalk-2.4.2.tgz?cache=0&sync_timestamp=1627646655305&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fchalk%2Fdownload%2Fchalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 466 | integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= 467 | dependencies: 468 | ansi-styles "^3.2.1" 469 | escape-string-regexp "^1.0.5" 470 | supports-color "^5.3.0" 471 | 472 | change-case@^4.1.2: 473 | version "4.1.2" 474 | resolved "https://registry.npm.taobao.org/change-case/download/change-case-4.1.2.tgz?cache=0&sync_timestamp=1606867326259&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchange-case%2Fdownload%2Fchange-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12" 475 | integrity sha1-/t/F8TYEXiOYwEEO5EH5VwRkHhI= 476 | dependencies: 477 | camel-case "^4.1.2" 478 | capital-case "^1.0.4" 479 | constant-case "^3.0.4" 480 | dot-case "^3.0.4" 481 | header-case "^2.0.4" 482 | no-case "^3.0.4" 483 | param-case "^3.0.4" 484 | pascal-case "^3.1.2" 485 | path-case "^3.0.4" 486 | sentence-case "^3.0.4" 487 | snake-case "^3.0.4" 488 | tslib "^2.0.3" 489 | 490 | "chokidar@>=3.0.0 <4.0.0": 491 | version "3.5.2" 492 | resolved "https://registry.nlark.com/chokidar/download/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 493 | integrity sha1-26OXb8rbAW9m/TZQIdkWANAcHnU= 494 | dependencies: 495 | anymatch "~3.1.2" 496 | braces "~3.0.2" 497 | glob-parent "~5.1.2" 498 | is-binary-path "~2.1.0" 499 | is-glob "~4.0.1" 500 | normalize-path "~3.0.0" 501 | readdirp "~3.6.0" 502 | optionalDependencies: 503 | fsevents "~2.3.2" 504 | 505 | classnames@*, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: 506 | version "2.3.1" 507 | resolved "https://registry.npm.taobao.org/classnames/download/classnames-2.3.1.tgz?cache=0&sync_timestamp=1617400318265&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fclassnames%2Fdownload%2Fclassnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" 508 | integrity sha1-38+jiR4wbsHa0QXQ6I9EF7hTXo4= 509 | 510 | color-convert@^1.9.0: 511 | version "1.9.3" 512 | resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 513 | integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg= 514 | dependencies: 515 | color-name "1.1.3" 516 | 517 | color-name@1.1.3: 518 | version "1.1.3" 519 | resolved "https://registry.nlark.com/color-name/download/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 520 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 521 | 522 | colorette@^1.2.2, colorette@^1.3.0: 523 | version "1.4.0" 524 | resolved "https://registry.nlark.com/colorette/download/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" 525 | integrity sha1-UZD7uHJ2JZqGrXAL/yxtb6o/ykA= 526 | 527 | compute-scroll-into-view@^1.0.17: 528 | version "1.0.17" 529 | resolved "https://registry.npm.taobao.org/compute-scroll-into-view/download/compute-scroll-into-view-1.0.17.tgz?cache=0&sync_timestamp=1614042283471&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompute-scroll-into-view%2Fdownload%2Fcompute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" 530 | integrity sha1-aojxis2dQunPS6pr7H4FImB6t6s= 531 | 532 | constant-case@^3.0.4: 533 | version "3.0.4" 534 | resolved "https://registry.npm.taobao.org/constant-case/download/constant-case-3.0.4.tgz?cache=0&sync_timestamp=1606867325763&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fconstant-case%2Fdownload%2Fconstant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" 535 | integrity sha1-O4Sprq9M8x7EXmv13pG9+wWJ+vE= 536 | dependencies: 537 | no-case "^3.0.4" 538 | tslib "^2.0.3" 539 | upper-case "^2.0.2" 540 | 541 | convert-source-map@^1.7.0: 542 | version "1.8.0" 543 | resolved "https://registry.nlark.com/convert-source-map/download/convert-source-map-1.8.0.tgz?cache=0&sync_timestamp=1624045304679&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fconvert-source-map%2Fdownload%2Fconvert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" 544 | integrity sha1-8zc8MtIbTXgN2ABFFGhPt5HKQ2k= 545 | dependencies: 546 | safe-buffer "~5.1.1" 547 | 548 | copy-anything@^2.0.1: 549 | version "2.0.3" 550 | resolved "https://registry.npm.taobao.org/copy-anything/download/copy-anything-2.0.3.tgz?cache=0&sync_timestamp=1612447490459&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcopy-anything%2Fdownload%2Fcopy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" 551 | integrity sha1-hCQHugJGaw34RIGbvjuuu+XUXYc= 552 | dependencies: 553 | is-what "^3.12.0" 554 | 555 | copy-to-clipboard@^3.2.0: 556 | version "3.3.1" 557 | resolved "https://registry.npm.taobao.org/copy-to-clipboard/download/copy-to-clipboard-3.3.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcopy-to-clipboard%2Fdownload%2Fcopy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" 558 | integrity sha1-EVqhqZmP+rYZb5MHatbaO5E2Yq4= 559 | dependencies: 560 | toggle-selection "^1.0.6" 561 | 562 | csstype@^3.0.2: 563 | version "3.0.9" 564 | resolved "https://registry.nlark.com/csstype/download/csstype-3.0.9.tgz?cache=0&sync_timestamp=1631540709509&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcsstype%2Fdownload%2Fcsstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" 565 | integrity sha1-ZBCvMbJr0FIJM9AsvGT86c4/vws= 566 | 567 | date-fns@2.x: 568 | version "2.24.0" 569 | resolved "https://registry.nlark.com/date-fns/download/date-fns-2.24.0.tgz#7d86dc0d93c87b76b63d213b4413337cfd1c105d" 570 | integrity sha1-fYbcDZPIe3a2PSE7RBMzfP0cEF0= 571 | 572 | dayjs@1.x: 573 | version "1.10.7" 574 | resolved "https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz?cache=0&sync_timestamp=1631266519235&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdayjs%2Fdownload%2Fdayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" 575 | integrity sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg= 576 | 577 | debug@^3.2.6: 578 | version "3.2.7" 579 | resolved "https://registry.nlark.com/debug/download/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 580 | integrity sha1-clgLfpFF+zm2Z2+cXl+xALk0F5o= 581 | dependencies: 582 | ms "^2.1.1" 583 | 584 | debug@^4.1.0, debug@^4.3.2: 585 | version "4.3.2" 586 | resolved "https://registry.nlark.com/debug/download/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" 587 | integrity sha1-8KScGKyHeeMdSgxgKd+3aHPHQos= 588 | dependencies: 589 | ms "2.1.2" 590 | 591 | deepcopy@^2.1.0: 592 | version "2.1.0" 593 | resolved "https://registry.npm.taobao.org/deepcopy/download/deepcopy-2.1.0.tgz#2deb0dd52d079c2ecb7924b640a7c3abd4db1d6d" 594 | integrity sha1-LesN1S0HnC7LeSS2QKfDq9TbHW0= 595 | dependencies: 596 | type-detect "^4.0.8" 597 | 598 | dom-align@^1.7.0: 599 | version "1.12.2" 600 | resolved "https://registry.nlark.com/dom-align/download/dom-align-1.12.2.tgz#0f8164ebd0c9c21b0c790310493cd855892acd4b" 601 | integrity sha1-D4Fk69DJwhsMeQMQSTzYVYkqzUs= 602 | 603 | dot-case@^3.0.4: 604 | version "3.0.4" 605 | resolved "https://registry.npm.taobao.org/dot-case/download/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" 606 | integrity sha1-mytnDQCkMWZ6inW6Kc0bmICc51E= 607 | dependencies: 608 | no-case "^3.0.4" 609 | tslib "^2.0.3" 610 | 611 | electron-to-chromium@^1.3.830: 612 | version "1.3.846" 613 | resolved "https://registry.nlark.com/electron-to-chromium/download/electron-to-chromium-1.3.846.tgz#a55fd59613dbcaed609e965e3e88f42b08c401d3" 614 | integrity sha1-pV/VlhPbyu1gnpZePoj0KwjEAdM= 615 | 616 | errno@^0.1.1: 617 | version "0.1.8" 618 | resolved "https://registry.npm.taobao.org/errno/download/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" 619 | integrity sha1-i7Ppx9Rjvkl2/4iPdrSAnrwugR8= 620 | dependencies: 621 | prr "~1.0.1" 622 | 623 | es-module-lexer@^0.7.1: 624 | version "0.7.1" 625 | resolved "https://registry.nlark.com/es-module-lexer/download/es-module-lexer-0.7.1.tgz#c2c8e0f46f2df06274cdaf0dd3f3b33e0a0b267d" 626 | integrity sha1-wsjg9G8t8GJ0za8N0/OzPgoLJn0= 627 | 628 | esbuild@^0.12.17: 629 | version "0.12.28" 630 | resolved "https://registry.nlark.com/esbuild/download/esbuild-0.12.28.tgz?cache=0&sync_timestamp=1631587318573&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fesbuild%2Fdownload%2Fesbuild-0.12.28.tgz#84da0d2a0d0dee181281545271e0d65cf6fab1ef" 631 | integrity sha1-hNoNKg0N7hgSgVRSceDWXPb6se8= 632 | 633 | escalade@^3.1.1: 634 | version "3.1.1" 635 | resolved "https://registry.npm.taobao.org/escalade/download/escalade-3.1.1.tgz?cache=0&sync_timestamp=1602567224085&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fescalade%2Fdownload%2Fescalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 636 | integrity sha1-2M/ccACWXFoBdLSoLqpcBVJ0LkA= 637 | 638 | escape-string-regexp@^1.0.5: 639 | version "1.0.5" 640 | resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz?cache=0&sync_timestamp=1618677243201&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fescape-string-regexp%2Fdownload%2Fescape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 641 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 642 | 643 | estree-walker@^2.0.1: 644 | version "2.0.2" 645 | resolved "https://registry.npm.taobao.org/estree-walker/download/estree-walker-2.0.2.tgz?cache=0&sync_timestamp=1611956983677&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Festree-walker%2Fdownload%2Festree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 646 | integrity sha1-UvAQF4wqTBF6d1fP6UKtt9LaTKw= 647 | 648 | fill-range@^7.0.1: 649 | version "7.0.1" 650 | resolved "https://registry.nlark.com/fill-range/download/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 651 | integrity sha1-GRmmp8df44ssfHflGYU12prN2kA= 652 | dependencies: 653 | to-regex-range "^5.0.1" 654 | 655 | fsevents@~2.3.2: 656 | version "2.3.2" 657 | resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz?cache=0&sync_timestamp=1612536512306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 658 | integrity sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro= 659 | 660 | function-bind@^1.1.1: 661 | version "1.1.1" 662 | resolved "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 663 | integrity sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0= 664 | 665 | gensync@^1.0.0-beta.2: 666 | version "1.0.0-beta.2" 667 | resolved "https://registry.nlark.com/gensync/download/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 668 | integrity sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA= 669 | 670 | glob-parent@~5.1.2: 671 | version "5.1.2" 672 | resolved "https://registry.nlark.com/glob-parent/download/glob-parent-5.1.2.tgz?cache=0&sync_timestamp=1626760165717&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fglob-parent%2Fdownload%2Fglob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 673 | integrity sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ= 674 | dependencies: 675 | is-glob "^4.0.1" 676 | 677 | globals@^11.1.0: 678 | version "11.12.0" 679 | resolved "https://registry.nlark.com/globals/download/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 680 | integrity sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4= 681 | 682 | graceful-fs@^4.1.2: 683 | version "4.2.8" 684 | resolved "https://registry.nlark.com/graceful-fs/download/graceful-fs-4.2.8.tgz?cache=0&sync_timestamp=1628194078324&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fgraceful-fs%2Fdownload%2Fgraceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" 685 | integrity sha1-5BK40z9eAGWTy9PO5t+fLOu+gCo= 686 | 687 | has-flag@^3.0.0: 688 | version "3.0.0" 689 | resolved "https://registry.nlark.com/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 690 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 691 | 692 | has@^1.0.3: 693 | version "1.0.3" 694 | resolved "https://registry.nlark.com/has/download/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 695 | integrity sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y= 696 | dependencies: 697 | function-bind "^1.1.1" 698 | 699 | header-case@^2.0.4: 700 | version "2.0.4" 701 | resolved "https://registry.npm.taobao.org/header-case/download/header-case-2.0.4.tgz?cache=0&sync_timestamp=1606867326152&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fheader-case%2Fdownload%2Fheader-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" 702 | integrity sha1-WkLmO1UXc0nPQFvrjXdayruSwGM= 703 | dependencies: 704 | capital-case "^1.0.4" 705 | tslib "^2.0.3" 706 | 707 | iconv-lite@^0.4.4: 708 | version "0.4.24" 709 | resolved "https://registry.nlark.com/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 710 | integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= 711 | dependencies: 712 | safer-buffer ">= 2.1.2 < 3" 713 | 714 | image-size@~0.5.0: 715 | version "0.5.5" 716 | resolved "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz?cache=0&sync_timestamp=1618422554012&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fimage-size%2Fdownload%2Fimage-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" 717 | integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= 718 | 719 | is-binary-path@~2.1.0: 720 | version "2.1.0" 721 | resolved "https://registry.nlark.com/is-binary-path/download/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 722 | integrity sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk= 723 | dependencies: 724 | binary-extensions "^2.0.0" 725 | 726 | is-core-module@^2.2.0: 727 | version "2.6.0" 728 | resolved "https://registry.nlark.com/is-core-module/download/is-core-module-2.6.0.tgz?cache=0&sync_timestamp=1629224656971&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fis-core-module%2Fdownload%2Fis-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" 729 | integrity sha1-11U7JSb+Wbkro+QMjfdX7Ipwnhk= 730 | dependencies: 731 | has "^1.0.3" 732 | 733 | is-extglob@^2.1.1: 734 | version "2.1.1" 735 | resolved "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 736 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 737 | 738 | is-glob@^4.0.1, is-glob@~4.0.1: 739 | version "4.0.1" 740 | resolved "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 741 | integrity sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw= 742 | dependencies: 743 | is-extglob "^2.1.1" 744 | 745 | is-number@^7.0.0: 746 | version "7.0.0" 747 | resolved "https://registry.nlark.com/is-number/download/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 748 | integrity sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss= 749 | 750 | is-what@^3.12.0: 751 | version "3.14.1" 752 | resolved "https://registry.npm.taobao.org/is-what/download/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" 753 | integrity sha1-4SIvRt3ahd6tD9HJ3xMXYOd3VcE= 754 | 755 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 756 | version "4.0.0" 757 | resolved "https://registry.nlark.com/js-tokens/download/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 758 | integrity sha1-GSA/tZmR35jjoocFDUZHzerzJJk= 759 | 760 | jsesc@^2.5.1: 761 | version "2.5.2" 762 | resolved "https://registry.npm.taobao.org/jsesc/download/jsesc-2.5.2.tgz?cache=0&sync_timestamp=1603891232110&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsesc%2Fdownload%2Fjsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 763 | integrity sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q= 764 | 765 | json2mq@^0.2.0: 766 | version "0.2.0" 767 | resolved "https://registry.npm.taobao.org/json2mq/download/json2mq-0.2.0.tgz?cache=0&sync_timestamp=1602340165972&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson2mq%2Fdownload%2Fjson2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" 768 | integrity sha1-tje9O6nqvhIsg+lyBIOusQ0skEo= 769 | dependencies: 770 | string-convert "^0.2.0" 771 | 772 | json5@^2.1.2: 773 | version "2.2.0" 774 | resolved "https://registry.npm.taobao.org/json5/download/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" 775 | integrity sha1-Lf7+cgxrpSXZ69kJlQ8FFTFsiaM= 776 | dependencies: 777 | minimist "^1.2.5" 778 | 779 | less@^4.1.1: 780 | version "4.1.1" 781 | resolved "https://registry.npm.taobao.org/less/download/less-4.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fless%2Fdownload%2Fless-4.1.1.tgz#15bf253a9939791dc690888c3ff424f3e6c7edba" 782 | integrity sha1-Fb8lOpk5eR3GkIiMP/Qk8+bH7bo= 783 | dependencies: 784 | copy-anything "^2.0.1" 785 | parse-node-version "^1.0.1" 786 | tslib "^1.10.0" 787 | optionalDependencies: 788 | errno "^0.1.1" 789 | graceful-fs "^4.1.2" 790 | image-size "~0.5.0" 791 | make-dir "^2.1.0" 792 | mime "^1.4.1" 793 | needle "^2.5.2" 794 | source-map "~0.6.0" 795 | 796 | lodash-es@^4.17.15: 797 | version "4.17.21" 798 | resolved "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.21.tgz?cache=0&sync_timestamp=1613835893273&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" 799 | integrity sha1-Q+YmxG5lkbd1C+srUBFzkMYJ4+4= 800 | 801 | lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.21: 802 | version "4.17.21" 803 | resolved "https://registry.nlark.com/lodash/download/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 804 | integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= 805 | 806 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: 807 | version "1.4.0" 808 | resolved "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 809 | integrity sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8= 810 | dependencies: 811 | js-tokens "^3.0.0 || ^4.0.0" 812 | 813 | lower-case@^2.0.2: 814 | version "2.0.2" 815 | resolved "https://registry.npm.taobao.org/lower-case/download/lower-case-2.0.2.tgz?cache=0&sync_timestamp=1606867333511&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flower-case%2Fdownload%2Flower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" 816 | integrity sha1-b6I3xj29xKgsoP2ILkci3F5jTig= 817 | dependencies: 818 | tslib "^2.0.3" 819 | 820 | magic-string@^0.25.7: 821 | version "0.25.7" 822 | resolved "https://registry.nlark.com/magic-string/download/magic-string-0.25.7.tgz?cache=0&sync_timestamp=1618847046304&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fmagic-string%2Fdownload%2Fmagic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 823 | integrity sha1-P0l9b9NMZpxnmNy4IfLvMfVEUFE= 824 | dependencies: 825 | sourcemap-codec "^1.4.4" 826 | 827 | make-dir@^2.1.0: 828 | version "2.1.0" 829 | resolved "https://registry.nlark.com/make-dir/download/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" 830 | integrity sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU= 831 | dependencies: 832 | pify "^4.0.1" 833 | semver "^5.6.0" 834 | 835 | material-colors@^1.2.1: 836 | version "1.2.6" 837 | resolved "https://registry.npm.taobao.org/material-colors/download/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" 838 | integrity sha1-bRlYhxEmmSzuzHL0vMTY8BCGX0Y= 839 | 840 | mime@^1.4.1: 841 | version "1.6.0" 842 | resolved "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 843 | integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE= 844 | 845 | minimist@^1.2.5: 846 | version "1.2.5" 847 | resolved "https://registry.nlark.com/minimist/download/minimist-1.2.5.tgz?cache=0&sync_timestamp=1618846813226&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fminimist%2Fdownload%2Fminimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 848 | integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI= 849 | 850 | moment@^2.24.0, moment@^2.25.3: 851 | version "2.29.1" 852 | resolved "https://registry.nlark.com/moment/download/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" 853 | integrity sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M= 854 | 855 | ms@2.1.2: 856 | version "2.1.2" 857 | resolved "https://registry.nlark.com/ms/download/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 858 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 859 | 860 | ms@^2.1.1: 861 | version "2.1.3" 862 | resolved "https://registry.nlark.com/ms/download/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 863 | integrity sha1-V0yBOM4dK1hh8LRFedut1gxmFbI= 864 | 865 | nanoid@^3.1.23: 866 | version "3.1.25" 867 | resolved "https://registry.nlark.com/nanoid/download/nanoid-3.1.25.tgz?cache=0&sync_timestamp=1628771905854&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnanoid%2Fdownload%2Fnanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" 868 | integrity sha1-CcoydHwOVD8OGBS303k0d/nI4VI= 869 | 870 | needle@^2.5.2: 871 | version "2.9.1" 872 | resolved "https://registry.nlark.com/needle/download/needle-2.9.1.tgz?cache=0&sync_timestamp=1630674912294&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fneedle%2Fdownload%2Fneedle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" 873 | integrity sha1-ItHf++NJDCuD4wH3cJtnNs2PJoQ= 874 | dependencies: 875 | debug "^3.2.6" 876 | iconv-lite "^0.4.4" 877 | sax "^1.2.4" 878 | 879 | no-case@^3.0.4: 880 | version "3.0.4" 881 | resolved "https://registry.nlark.com/no-case/download/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" 882 | integrity sha1-02H9XJgA9VhVGoNp/A3NRmK2Ek0= 883 | dependencies: 884 | lower-case "^2.0.2" 885 | tslib "^2.0.3" 886 | 887 | node-releases@^1.1.75: 888 | version "1.1.76" 889 | resolved "https://registry.nlark.com/node-releases/download/node-releases-1.1.76.tgz?cache=0&sync_timestamp=1632151316505&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e" 890 | integrity sha1-3yRbBisMr71SgqtnkvfczC2X824= 891 | 892 | normalize-path@^3.0.0, normalize-path@~3.0.0: 893 | version "3.0.0" 894 | resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 895 | integrity sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU= 896 | 897 | object-assign@^4.1.1: 898 | version "4.1.1" 899 | resolved "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 900 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 901 | 902 | param-case@^3.0.4: 903 | version "3.0.4" 904 | resolved "https://registry.nlark.com/param-case/download/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" 905 | integrity sha1-fRf+SqEr3jTUp32RrPtiGcqtAcU= 906 | dependencies: 907 | dot-case "^3.0.4" 908 | tslib "^2.0.3" 909 | 910 | parse-node-version@^1.0.1: 911 | version "1.0.1" 912 | resolved "https://registry.npm.taobao.org/parse-node-version/download/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" 913 | integrity sha1-4rXb7eAOf6m8NjYH9TMn6LBzGJs= 914 | 915 | pascal-case@^3.1.2: 916 | version "3.1.2" 917 | resolved "https://registry.nlark.com/pascal-case/download/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" 918 | integrity sha1-tI4O8rmOIF58Ha50fQsVCCN2YOs= 919 | dependencies: 920 | no-case "^3.0.4" 921 | tslib "^2.0.3" 922 | 923 | path-case@^3.0.4: 924 | version "3.0.4" 925 | resolved "https://registry.npm.taobao.org/path-case/download/path-case-3.0.4.tgz?cache=0&sync_timestamp=1606867325967&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpath-case%2Fdownload%2Fpath-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" 926 | integrity sha1-kWhkUzTrlCZYN1xW+AtMDLX4LG8= 927 | dependencies: 928 | dot-case "^3.0.4" 929 | tslib "^2.0.3" 930 | 931 | path-parse@^1.0.6: 932 | version "1.0.7" 933 | resolved "https://registry.nlark.com/path-parse/download/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 934 | integrity sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU= 935 | 936 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: 937 | version "2.3.0" 938 | resolved "https://registry.nlark.com/picomatch/download/picomatch-2.3.0.tgz?cache=0&sync_timestamp=1621648246651&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpicomatch%2Fdownload%2Fpicomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 939 | integrity sha1-8fBh3o9qS/AiiS4tEoI0+5gwKXI= 940 | 941 | pify@^4.0.1: 942 | version "4.0.1" 943 | resolved "https://registry.nlark.com/pify/download/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" 944 | integrity sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE= 945 | 946 | postcss@^8.3.6: 947 | version "8.3.6" 948 | resolved "https://registry.nlark.com/postcss/download/postcss-8.3.6.tgz?cache=0&sync_timestamp=1626882933935&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss%2Fdownload%2Fpostcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" 949 | integrity sha1-JzDddql5afN/U7mmCWGXvjEcxOo= 950 | dependencies: 951 | colorette "^1.2.2" 952 | nanoid "^3.1.23" 953 | source-map-js "^0.6.2" 954 | 955 | prop-types@^15.5.10: 956 | version "15.7.2" 957 | resolved "https://registry.npm.taobao.org/prop-types/download/prop-types-15.7.2.tgz?cache=0&sync_timestamp=1615984425557&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fprop-types%2Fdownload%2Fprop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" 958 | integrity sha1-UsQedbjIfnK52TYOAga5ncv/psU= 959 | dependencies: 960 | loose-envify "^1.4.0" 961 | object-assign "^4.1.1" 962 | react-is "^16.8.1" 963 | 964 | prr@~1.0.1: 965 | version "1.0.1" 966 | resolved "https://registry.npm.taobao.org/prr/download/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" 967 | integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= 968 | 969 | rc-align@^4.0.0: 970 | version "4.0.11" 971 | resolved "https://registry.nlark.com/rc-align/download/rc-align-4.0.11.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-align%2Fdownload%2Frc-align-4.0.11.tgz#8198c62db266bc1b8ef05e56c13275bf72628a5e" 972 | integrity sha1-gZjGLbJmvBuO8F5WwTJ1v3Jiil4= 973 | dependencies: 974 | "@babel/runtime" "^7.10.1" 975 | classnames "2.x" 976 | dom-align "^1.7.0" 977 | lodash "^4.17.21" 978 | rc-util "^5.3.0" 979 | resize-observer-polyfill "^1.5.1" 980 | 981 | rc-cascader@~1.4.0: 982 | version "1.4.3" 983 | resolved "https://registry.nlark.com/rc-cascader/download/rc-cascader-1.4.3.tgz?cache=0&sync_timestamp=1631514687405&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-cascader%2Fdownload%2Frc-cascader-1.4.3.tgz#d91b0dcf8157b60ebe9ec3e58b4db054d5299464" 984 | integrity sha1-2RsNz4FXtg6+nsPli02wVNUplGQ= 985 | dependencies: 986 | "@babel/runtime" "^7.12.5" 987 | array-tree-filter "^2.1.0" 988 | rc-trigger "^5.0.4" 989 | rc-util "^5.0.1" 990 | warning "^4.0.1" 991 | 992 | rc-checkbox@~2.3.0: 993 | version "2.3.2" 994 | resolved "https://registry.npm.taobao.org/rc-checkbox/download/rc-checkbox-2.3.2.tgz?cache=0&sync_timestamp=1607402886550&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-checkbox%2Fdownload%2Frc-checkbox-2.3.2.tgz#f91b3678c7edb2baa8121c9483c664fa6f0aefc1" 995 | integrity sha1-+Rs2eMftsrqoEhyUg8Zk+m8K78E= 996 | dependencies: 997 | "@babel/runtime" "^7.10.1" 998 | classnames "^2.2.1" 999 | 1000 | rc-collapse@~3.1.0: 1001 | version "3.1.2" 1002 | resolved "https://registry.nlark.com/rc-collapse/download/rc-collapse-3.1.2.tgz#76028a811b845d03d9460ccc409c7ea8ad09db14" 1003 | integrity sha1-dgKKgRuEXQPZRgzMQJx+qK0J2xQ= 1004 | dependencies: 1005 | "@babel/runtime" "^7.10.1" 1006 | classnames "2.x" 1007 | rc-motion "^2.3.4" 1008 | rc-util "^5.2.1" 1009 | shallowequal "^1.1.0" 1010 | 1011 | rc-dialog@~8.6.0: 1012 | version "8.6.0" 1013 | resolved "https://registry.nlark.com/rc-dialog/download/rc-dialog-8.6.0.tgz?cache=0&sync_timestamp=1627272051827&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-dialog%2Fdownload%2Frc-dialog-8.6.0.tgz#3b228dac085de5eed8c6237f31162104687442e7" 1014 | integrity sha1-OyKNrAhd5e7YxiN/MRYhBGh0Quc= 1015 | dependencies: 1016 | "@babel/runtime" "^7.10.1" 1017 | classnames "^2.2.6" 1018 | rc-motion "^2.3.0" 1019 | rc-util "^5.6.1" 1020 | 1021 | rc-drawer@~4.3.0: 1022 | version "4.3.1" 1023 | resolved "https://registry.nlark.com/rc-drawer/download/rc-drawer-4.3.1.tgz?cache=0&sync_timestamp=1630560040693&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-drawer%2Fdownload%2Frc-drawer-4.3.1.tgz#356333a7af01b777abd685c96c2ce62efb44f3f3" 1024 | integrity sha1-NWMzp68Bt3er1oXJbCzmLvtE8/M= 1025 | dependencies: 1026 | "@babel/runtime" "^7.10.1" 1027 | classnames "^2.2.6" 1028 | rc-util "^5.7.0" 1029 | 1030 | rc-dropdown@^3.2.0, rc-dropdown@~3.2.0: 1031 | version "3.2.0" 1032 | resolved "https://registry.npm.taobao.org/rc-dropdown/download/rc-dropdown-3.2.0.tgz?cache=0&sync_timestamp=1600332823107&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-dropdown%2Fdownload%2Frc-dropdown-3.2.0.tgz#da6c2ada403842baee3a9e909a0b1a91ba3e1090" 1033 | integrity sha1-2mwq2kA4QrruOp6Qmgsakbo+EJA= 1034 | dependencies: 1035 | "@babel/runtime" "^7.10.1" 1036 | classnames "^2.2.6" 1037 | rc-trigger "^5.0.4" 1038 | 1039 | rc-field-form@~1.20.0: 1040 | version "1.20.1" 1041 | resolved "https://registry.nlark.com/rc-field-form/download/rc-field-form-1.20.1.tgz#d1c51888107cf075b42704b7b575bef84c359291" 1042 | integrity sha1-0cUYiBB88HW0JwS3tXW++Ew1kpE= 1043 | dependencies: 1044 | "@babel/runtime" "^7.8.4" 1045 | async-validator "^3.0.3" 1046 | rc-util "^5.8.0" 1047 | 1048 | rc-image@~5.2.5: 1049 | version "5.2.5" 1050 | resolved "https://registry.nlark.com/rc-image/download/rc-image-5.2.5.tgz?cache=0&sync_timestamp=1627889032895&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-image%2Fdownload%2Frc-image-5.2.5.tgz#44e6ffc842626827960e7ab72e1c0d6f3a8ce440" 1051 | integrity sha1-ROb/yEJiaCeWDnq3LhwNbzqM5EA= 1052 | dependencies: 1053 | "@babel/runtime" "^7.11.2" 1054 | classnames "^2.2.6" 1055 | rc-dialog "~8.6.0" 1056 | rc-util "^5.0.6" 1057 | 1058 | rc-input-number@~7.1.0: 1059 | version "7.1.4" 1060 | resolved "https://registry.nlark.com/rc-input-number/download/rc-input-number-7.1.4.tgz?cache=0&sync_timestamp=1631779750767&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-input-number%2Fdownload%2Frc-input-number-7.1.4.tgz#9d7410c91ff8dc6384d0233c20df278982989f9a" 1061 | integrity sha1-nXQQyR/43GOE0CM8IN8niYKYn5o= 1062 | dependencies: 1063 | "@babel/runtime" "^7.10.1" 1064 | classnames "^2.2.5" 1065 | rc-util "^5.9.8" 1066 | 1067 | rc-mentions@~1.6.1: 1068 | version "1.6.1" 1069 | resolved "https://registry.nlark.com/rc-mentions/download/rc-mentions-1.6.1.tgz#46035027d64aa33ef840ba0fbd411871e34617ae" 1070 | integrity sha1-RgNQJ9ZKoz74QLoPvUEYceNGF64= 1071 | dependencies: 1072 | "@babel/runtime" "^7.10.1" 1073 | classnames "^2.2.6" 1074 | rc-menu "^9.0.0" 1075 | rc-textarea "^0.3.0" 1076 | rc-trigger "^5.0.4" 1077 | rc-util "^5.0.1" 1078 | 1079 | rc-menu@^9.0.0, rc-menu@~9.0.12: 1080 | version "9.0.12" 1081 | resolved "https://registry.nlark.com/rc-menu/download/rc-menu-9.0.12.tgz#492c4bb07a596e2ce07587c669b27ee28c3810c5" 1082 | integrity sha1-SSxLsHpZbizgdYfGabJ+4ow4EMU= 1083 | dependencies: 1084 | "@babel/runtime" "^7.10.1" 1085 | classnames "2.x" 1086 | rc-motion "^2.4.3" 1087 | rc-overflow "^1.2.0" 1088 | rc-trigger "^5.1.2" 1089 | rc-util "^5.12.0" 1090 | shallowequal "^1.1.0" 1091 | 1092 | rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.0, rc-motion@^2.4.3: 1093 | version "2.4.4" 1094 | resolved "https://registry.nlark.com/rc-motion/download/rc-motion-2.4.4.tgz?cache=0&sync_timestamp=1622690263011&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-motion%2Fdownload%2Frc-motion-2.4.4.tgz#e995d5fa24fc93065c24f714857cf2677d655bb0" 1095 | integrity sha1-6ZXV+iT8kwZcJPcUhXzyZ31lW7A= 1096 | dependencies: 1097 | "@babel/runtime" "^7.11.1" 1098 | classnames "^2.2.1" 1099 | rc-util "^5.2.1" 1100 | 1101 | rc-notification@~4.5.7: 1102 | version "4.5.7" 1103 | resolved "https://registry.nlark.com/rc-notification/download/rc-notification-4.5.7.tgz#265e6e6a0c1a0fac63d6abd4d832eb8ff31522f1" 1104 | integrity sha1-Jl5uagwaD6xj1qvU2DLrj/MVIvE= 1105 | dependencies: 1106 | "@babel/runtime" "^7.10.1" 1107 | classnames "2.x" 1108 | rc-motion "^2.2.0" 1109 | rc-util "^5.0.1" 1110 | 1111 | rc-overflow@^1.0.0, rc-overflow@^1.2.0: 1112 | version "1.2.2" 1113 | resolved "https://registry.nlark.com/rc-overflow/download/rc-overflow-1.2.2.tgz#95b0222016c0cdbdc0db85f569c262e7706a5f22" 1114 | integrity sha1-lbAiIBbAzb3A24X1acJi53BqXyI= 1115 | dependencies: 1116 | "@babel/runtime" "^7.11.1" 1117 | classnames "^2.2.1" 1118 | rc-resize-observer "^1.0.0" 1119 | rc-util "^5.5.1" 1120 | 1121 | rc-pagination@~3.1.9: 1122 | version "3.1.9" 1123 | resolved "https://registry.nlark.com/rc-pagination/download/rc-pagination-3.1.9.tgz#797ad75d85b1ef7a82801207ead410110337fdd6" 1124 | integrity sha1-eXrXXYWx73qCgBIH6tQQEQM3/dY= 1125 | dependencies: 1126 | "@babel/runtime" "^7.10.1" 1127 | classnames "^2.2.1" 1128 | 1129 | rc-picker@~2.5.10: 1130 | version "2.5.18" 1131 | resolved "https://registry.nlark.com/rc-picker/download/rc-picker-2.5.18.tgz#f84859815ef3f874ade689714a41151e709292c3" 1132 | integrity sha1-+EhZgV7z+HSt5olxSkEVHnCSksM= 1133 | dependencies: 1134 | "@babel/runtime" "^7.10.1" 1135 | classnames "^2.2.1" 1136 | date-fns "2.x" 1137 | dayjs "1.x" 1138 | moment "^2.24.0" 1139 | rc-trigger "^5.0.4" 1140 | rc-util "^5.4.0" 1141 | shallowequal "^1.1.0" 1142 | 1143 | rc-progress@~3.1.0: 1144 | version "3.1.4" 1145 | resolved "https://registry.nlark.com/rc-progress/download/rc-progress-3.1.4.tgz#66040d0fae7d8ced2b38588378eccb2864bad615" 1146 | integrity sha1-ZgQND659jO0rOFiDeOzLKGS61hU= 1147 | dependencies: 1148 | "@babel/runtime" "^7.10.1" 1149 | classnames "^2.2.6" 1150 | 1151 | rc-rate@~2.9.0: 1152 | version "2.9.1" 1153 | resolved "https://registry.npm.taobao.org/rc-rate/download/rc-rate-2.9.1.tgz?cache=0&sync_timestamp=1605573484361&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-rate%2Fdownload%2Frc-rate-2.9.1.tgz#e43cb95c4eb90a2c1e0b16ec6614d8c43530a731" 1154 | integrity sha1-5Dy5XE65CiweCxbsZhTYxDUwpzE= 1155 | dependencies: 1156 | "@babel/runtime" "^7.10.1" 1157 | classnames "^2.2.5" 1158 | rc-util "^5.0.1" 1159 | 1160 | rc-resize-observer@^1.0.0: 1161 | version "1.0.1" 1162 | resolved "https://registry.nlark.com/rc-resize-observer/download/rc-resize-observer-1.0.1.tgz?cache=0&sync_timestamp=1630563985540&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-resize-observer%2Fdownload%2Frc-resize-observer-1.0.1.tgz#ccd0986543ff1bf49f8a581e8ac4bb714ed24dcd" 1163 | integrity sha1-zNCYZUP/G/SfilgeisS7cU7STc0= 1164 | dependencies: 1165 | "@babel/runtime" "^7.10.1" 1166 | classnames "^2.2.1" 1167 | rc-util "^5.0.0" 1168 | resize-observer-polyfill "^1.5.1" 1169 | 1170 | rc-select@^12.0.0, rc-select@~12.1.6: 1171 | version "12.1.13" 1172 | resolved "https://registry.nlark.com/rc-select/download/rc-select-12.1.13.tgz#c33560ccb9339d30695b52458f55efc35af35273" 1173 | integrity sha1-wzVgzLkznTBpW1JFj1Xvw1rzUnM= 1174 | dependencies: 1175 | "@babel/runtime" "^7.10.1" 1176 | classnames "2.x" 1177 | rc-motion "^2.0.1" 1178 | rc-overflow "^1.0.0" 1179 | rc-trigger "^5.0.4" 1180 | rc-util "^5.9.8" 1181 | rc-virtual-list "^3.2.0" 1182 | 1183 | rc-slider@~9.7.1: 1184 | version "9.7.2" 1185 | resolved "https://registry.npm.taobao.org/rc-slider/download/rc-slider-9.7.2.tgz?cache=0&sync_timestamp=1616675519253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-slider%2Fdownload%2Frc-slider-9.7.2.tgz#282f571f7582752ebaa33964e441184f4e79ad74" 1186 | integrity sha1-KC9XH3WCdS66ozlk5EEYT055rXQ= 1187 | dependencies: 1188 | "@babel/runtime" "^7.10.1" 1189 | classnames "^2.2.5" 1190 | rc-tooltip "^5.0.1" 1191 | rc-util "^5.0.0" 1192 | shallowequal "^1.1.0" 1193 | 1194 | rc-steps@~4.1.0: 1195 | version "4.1.3" 1196 | resolved "https://registry.npm.taobao.org/rc-steps/download/rc-steps-4.1.3.tgz?cache=0&sync_timestamp=1603284176603&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frc-steps%2Fdownload%2Frc-steps-4.1.3.tgz#208580e22db619e3830ddb7fa41bc886c65d9803" 1197 | integrity sha1-IIWA4i22GeODDdt/pBvIhsZdmAM= 1198 | dependencies: 1199 | "@babel/runtime" "^7.10.2" 1200 | classnames "^2.2.3" 1201 | rc-util "^5.0.1" 1202 | 1203 | rc-switch@~3.2.0: 1204 | version "3.2.2" 1205 | resolved "https://registry.nlark.com/rc-switch/download/rc-switch-3.2.2.tgz#d001f77f12664d52595b4f6fb425dd9e66fba8e8" 1206 | integrity sha1-0AH3fxJmTVJZW09vtCXdnmb7qOg= 1207 | dependencies: 1208 | "@babel/runtime" "^7.10.1" 1209 | classnames "^2.2.1" 1210 | rc-util "^5.0.1" 1211 | 1212 | rc-table@~7.15.1: 1213 | version "7.15.2" 1214 | resolved "https://registry.nlark.com/rc-table/download/rc-table-7.15.2.tgz?cache=0&sync_timestamp=1631688916566&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-table%2Fdownload%2Frc-table-7.15.2.tgz#f6ab73b2cfb1c76f3cf9682c855561423c6b5b22" 1215 | integrity sha1-9qtzss+xx288+WgshVVhQjxrWyI= 1216 | dependencies: 1217 | "@babel/runtime" "^7.10.1" 1218 | classnames "^2.2.5" 1219 | rc-resize-observer "^1.0.0" 1220 | rc-util "^5.13.0" 1221 | shallowequal "^1.1.0" 1222 | 1223 | rc-tabs@~11.10.0: 1224 | version "11.10.1" 1225 | resolved "https://registry.nlark.com/rc-tabs/download/rc-tabs-11.10.1.tgz#7b112f78bac998480c777ae160adc425e3fdb7cb" 1226 | integrity sha1-exEveLrJmEgMd3rhYK3EJeP9t8s= 1227 | dependencies: 1228 | "@babel/runtime" "^7.11.2" 1229 | classnames "2.x" 1230 | rc-dropdown "^3.2.0" 1231 | rc-menu "^9.0.0" 1232 | rc-resize-observer "^1.0.0" 1233 | rc-util "^5.5.0" 1234 | 1235 | rc-textarea@^0.3.0, rc-textarea@~0.3.0: 1236 | version "0.3.5" 1237 | resolved "https://registry.nlark.com/rc-textarea/download/rc-textarea-0.3.5.tgz#07ed445dddb94e5ae6764676923a49bddad9b2ec" 1238 | integrity sha1-B+1EXd25TlrmdkZ2kjpJvdrZsuw= 1239 | dependencies: 1240 | "@babel/runtime" "^7.10.1" 1241 | classnames "^2.2.1" 1242 | rc-resize-observer "^1.0.0" 1243 | rc-util "^5.7.0" 1244 | 1245 | rc-tooltip@^5.0.1, rc-tooltip@~5.1.1: 1246 | version "5.1.1" 1247 | resolved "https://registry.nlark.com/rc-tooltip/download/rc-tooltip-5.1.1.tgz?cache=0&sync_timestamp=1620376922725&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-tooltip%2Fdownload%2Frc-tooltip-5.1.1.tgz#94178ed162d0252bc4993b725f5dc2ac0fccf154" 1248 | integrity sha1-lBeO0WLQJSvEmTtyX13CrA/M8VQ= 1249 | dependencies: 1250 | "@babel/runtime" "^7.11.2" 1251 | rc-trigger "^5.0.0" 1252 | 1253 | rc-tree-select@~4.3.0: 1254 | version "4.3.3" 1255 | resolved "https://registry.nlark.com/rc-tree-select/download/rc-tree-select-4.3.3.tgz?cache=0&sync_timestamp=1631588798554&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-tree-select%2Fdownload%2Frc-tree-select-4.3.3.tgz#28eba4d8a8dc8c0f9b61d83ce465842a6915eca4" 1256 | integrity sha1-KOuk2KjcjA+bYdg85GWEKmkV7KQ= 1257 | dependencies: 1258 | "@babel/runtime" "^7.10.1" 1259 | classnames "2.x" 1260 | rc-select "^12.0.0" 1261 | rc-tree "^4.0.0" 1262 | rc-util "^5.0.5" 1263 | 1264 | rc-tree@^4.0.0, rc-tree@~4.2.1: 1265 | version "4.2.2" 1266 | resolved "https://registry.nlark.com/rc-tree/download/rc-tree-4.2.2.tgz?cache=0&sync_timestamp=1630923510026&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-tree%2Fdownload%2Frc-tree-4.2.2.tgz#4429187cbbfbecbe989714a607e3de8b3ab7763f" 1267 | integrity sha1-RCkYfLv77L6YlxSmB+Peizq3dj8= 1268 | dependencies: 1269 | "@babel/runtime" "^7.10.1" 1270 | classnames "2.x" 1271 | rc-motion "^2.0.1" 1272 | rc-util "^5.0.0" 1273 | rc-virtual-list "^3.0.1" 1274 | 1275 | rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.10: 1276 | version "5.2.10" 1277 | resolved "https://registry.nlark.com/rc-trigger/download/rc-trigger-5.2.10.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-trigger%2Fdownload%2Frc-trigger-5.2.10.tgz#8a0057a940b1b9027eaa33beec8a6ecd85cce2b1" 1278 | integrity sha1-igBXqUCxuQJ+qjO+7IpuzYXM4rE= 1279 | dependencies: 1280 | "@babel/runtime" "^7.11.2" 1281 | classnames "^2.2.6" 1282 | rc-align "^4.0.0" 1283 | rc-motion "^2.0.0" 1284 | rc-util "^5.5.0" 1285 | 1286 | rc-upload@~4.3.0: 1287 | version "4.3.1" 1288 | resolved "https://registry.nlark.com/rc-upload/download/rc-upload-4.3.1.tgz?cache=0&sync_timestamp=1623401069520&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-upload%2Fdownload%2Frc-upload-4.3.1.tgz#d6ee66b8bd1e1dd2f78526c486538423f7e7ed84" 1289 | integrity sha1-1u5muL0eHdL3hSbEhlOEI/fn7YQ= 1290 | dependencies: 1291 | "@babel/runtime" "^7.10.1" 1292 | classnames "^2.2.5" 1293 | rc-util "^5.2.0" 1294 | 1295 | rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.5, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.12.0, rc-util@^5.13.0, rc-util@^5.13.1, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.6.1, rc-util@^5.7.0, rc-util@^5.8.0, rc-util@^5.9.4, rc-util@^5.9.8: 1296 | version "5.14.0" 1297 | resolved "https://registry.nlark.com/rc-util/download/rc-util-5.14.0.tgz?cache=0&sync_timestamp=1631611509279&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-util%2Fdownload%2Frc-util-5.14.0.tgz#52c650e27570c2c47f7936c7d32eaec5212492a8" 1298 | integrity sha1-UsZQ4nVwwsR/eTbH0y6uxSEkkqg= 1299 | dependencies: 1300 | "@babel/runtime" "^7.12.5" 1301 | react-is "^16.12.0" 1302 | shallowequal "^1.1.0" 1303 | 1304 | rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0: 1305 | version "3.4.1" 1306 | resolved "https://registry.nlark.com/rc-virtual-list/download/rc-virtual-list-3.4.1.tgz?cache=0&sync_timestamp=1630653487624&other_urls=https%3A%2F%2Fregistry.nlark.com%2Frc-virtual-list%2Fdownload%2Frc-virtual-list-3.4.1.tgz#1f3b41391acf033a6c7e84c2f4e8a4ee0dc72807" 1307 | integrity sha1-HztBORrPAzpsfoTC9Oik7g3HKAc= 1308 | dependencies: 1309 | classnames "^2.2.6" 1310 | rc-resize-observer "^1.0.0" 1311 | rc-util "^5.0.7" 1312 | 1313 | react-color@^2.19.3: 1314 | version "2.19.3" 1315 | resolved "https://registry.nlark.com/react-color/download/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d" 1316 | integrity sha1-7GxrRWgxKjxqGEIKsEcuFGqlaD0= 1317 | dependencies: 1318 | "@icons/material" "^0.2.4" 1319 | lodash "^4.17.15" 1320 | lodash-es "^4.17.15" 1321 | material-colors "^1.2.1" 1322 | prop-types "^15.5.10" 1323 | reactcss "^1.2.0" 1324 | tinycolor2 "^1.4.1" 1325 | 1326 | react-dom@^17.0.1: 1327 | version "17.0.2" 1328 | resolved "https://registry.nlark.com/react-dom/download/react-dom-17.0.2.tgz?cache=0&sync_timestamp=1632264147314&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-dom%2Fdownload%2Freact-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" 1329 | integrity sha1-7P+2hF462Nv83EmPDQqTlzZQLCM= 1330 | dependencies: 1331 | loose-envify "^1.1.0" 1332 | object-assign "^4.1.1" 1333 | scheduler "^0.20.2" 1334 | 1335 | react-is@^16.12.0, react-is@^16.8.1: 1336 | version "16.13.1" 1337 | resolved "https://registry.nlark.com/react-is/download/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 1338 | integrity sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ= 1339 | 1340 | react-refresh@^0.10.0: 1341 | version "0.10.0" 1342 | resolved "https://registry.nlark.com/react-refresh/download/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" 1343 | integrity sha1-L1NslmDAubHVAGhNnlKmXnQE9+M= 1344 | 1345 | react@^17.0.1: 1346 | version "17.0.2" 1347 | resolved "https://registry.nlark.com/react/download/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" 1348 | integrity sha1-0LXMUW0p6z7uOD91tihkz7aAADc= 1349 | dependencies: 1350 | loose-envify "^1.1.0" 1351 | object-assign "^4.1.1" 1352 | 1353 | reactcss@^1.2.0: 1354 | version "1.2.3" 1355 | resolved "https://registry.npm.taobao.org/reactcss/download/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" 1356 | integrity sha1-wAATh15Vexzw39mjaKHD2rO1SN0= 1357 | dependencies: 1358 | lodash "^4.0.1" 1359 | 1360 | readdirp@~3.6.0: 1361 | version "3.6.0" 1362 | resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.6.0.tgz?cache=0&sync_timestamp=1615717369278&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1363 | integrity sha1-dKNwvYVxFuJFspzJc0DNQxoCpsc= 1364 | dependencies: 1365 | picomatch "^2.2.1" 1366 | 1367 | regenerator-runtime@^0.13.4: 1368 | version "0.13.9" 1369 | resolved "https://registry.nlark.com/regenerator-runtime/download/regenerator-runtime-0.13.9.tgz?cache=0&sync_timestamp=1626993001371&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" 1370 | integrity sha1-iSV0Kpj/2QgUmI11Zq0wyjsmO1I= 1371 | 1372 | resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1: 1373 | version "1.5.1" 1374 | resolved "https://registry.npm.taobao.org/resize-observer-polyfill/download/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" 1375 | integrity sha1-DpAg3T0hAkRY1OvSfiPkAmmBBGQ= 1376 | 1377 | resolve@^1.20.0: 1378 | version "1.20.0" 1379 | resolved "https://registry.nlark.com/resolve/download/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 1380 | integrity sha1-YpoBP7P3B1XW8LeTXMHCxTeLGXU= 1381 | dependencies: 1382 | is-core-module "^2.2.0" 1383 | path-parse "^1.0.6" 1384 | 1385 | rollup@^2.38.5: 1386 | version "2.56.3" 1387 | resolved "https://registry.nlark.com/rollup/download/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff" 1388 | integrity sha1-tj7a3ZhRsNYYptDmr4IBlVp3rv8= 1389 | optionalDependencies: 1390 | fsevents "~2.3.2" 1391 | 1392 | safe-buffer@~5.1.1: 1393 | version "5.1.2" 1394 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 1395 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 1396 | 1397 | "safer-buffer@>= 2.1.2 < 3": 1398 | version "2.1.2" 1399 | resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1400 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 1401 | 1402 | sass@^1.42.0: 1403 | version "1.42.0" 1404 | resolved "https://registry.nlark.com/sass/download/sass-1.42.0.tgz#dc485e5cf7890a22fd6ccc6856c64f297239d595" 1405 | integrity sha1-3EheXPeJCiL9bMxoVsZPKXI51ZU= 1406 | dependencies: 1407 | chokidar ">=3.0.0 <4.0.0" 1408 | 1409 | sax@^1.2.4: 1410 | version "1.2.4" 1411 | resolved "https://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 1412 | integrity sha1-KBYjTiN4vdxOU1T6tcqold9xANk= 1413 | 1414 | scheduler@^0.20.2: 1415 | version "0.20.2" 1416 | resolved "https://registry.nlark.com/scheduler/download/scheduler-0.20.2.tgz?cache=0&sync_timestamp=1632264118861&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fscheduler%2Fdownload%2Fscheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" 1417 | integrity sha1-S67jlDbjSqk7SHS93L8P6Li1DpE= 1418 | dependencies: 1419 | loose-envify "^1.1.0" 1420 | object-assign "^4.1.1" 1421 | 1422 | scroll-into-view-if-needed@^2.2.25: 1423 | version "2.2.28" 1424 | resolved "https://registry.npm.taobao.org/scroll-into-view-if-needed/download/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a" 1425 | integrity sha1-WhWy9YpSZCyIyOylhGROAXA9ZFo= 1426 | dependencies: 1427 | compute-scroll-into-view "^1.0.17" 1428 | 1429 | semver@^5.6.0: 1430 | version "5.7.1" 1431 | resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1432 | integrity sha1-qVT5Ma66UI0we78Gnv8MAclhFvc= 1433 | 1434 | semver@^6.3.0: 1435 | version "6.3.0" 1436 | resolved "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1437 | integrity sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0= 1438 | 1439 | sentence-case@^3.0.4: 1440 | version "3.0.4" 1441 | resolved "https://registry.npm.taobao.org/sentence-case/download/sentence-case-3.0.4.tgz?cache=0&sync_timestamp=1606867325535&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsentence-case%2Fdownload%2Fsentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" 1442 | integrity sha1-NkWnuMEXx4f96HAgViJbtipFEx8= 1443 | dependencies: 1444 | no-case "^3.0.4" 1445 | tslib "^2.0.3" 1446 | upper-case-first "^2.0.2" 1447 | 1448 | shallowequal@^1.1.0: 1449 | version "1.1.0" 1450 | resolved "https://registry.npm.taobao.org/shallowequal/download/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" 1451 | integrity sha1-GI1SHelbkIdAT9TctosT3wrk5/g= 1452 | 1453 | snake-case@^3.0.4: 1454 | version "3.0.4" 1455 | resolved "https://registry.npm.taobao.org/snake-case/download/snake-case-3.0.4.tgz?cache=0&sync_timestamp=1606867326057&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsnake-case%2Fdownload%2Fsnake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" 1456 | integrity sha1-Tyu9Vo6ZNavf1ZPzTGkdrbScRSw= 1457 | dependencies: 1458 | dot-case "^3.0.4" 1459 | tslib "^2.0.3" 1460 | 1461 | source-map-js@^0.6.2: 1462 | version "0.6.2" 1463 | resolved "https://registry.npm.taobao.org/source-map-js/download/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 1464 | integrity sha1-C7XeYxtBz72mz7qL0FqA79/SOF4= 1465 | 1466 | source-map@^0.5.0: 1467 | version "0.5.7" 1468 | resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1469 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 1470 | 1471 | source-map@~0.6.0: 1472 | version "0.6.1" 1473 | resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1474 | integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= 1475 | 1476 | sourcemap-codec@^1.4.4: 1477 | version "1.4.8" 1478 | resolved "https://registry.npm.taobao.org/sourcemap-codec/download/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 1479 | integrity sha1-6oBL2UhXQC5pktBaOO8a41qatMQ= 1480 | 1481 | string-convert@^0.2.0: 1482 | version "0.2.1" 1483 | resolved "https://registry.nlark.com/string-convert/download/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" 1484 | integrity sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c= 1485 | 1486 | supports-color@^5.3.0: 1487 | version "5.5.0" 1488 | resolved "https://registry.nlark.com/supports-color/download/supports-color-5.5.0.tgz?cache=0&sync_timestamp=1626703414084&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsupports-color%2Fdownload%2Fsupports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1489 | integrity sha1-4uaaRKyHcveKHsCzW2id9lMO/I8= 1490 | dependencies: 1491 | has-flag "^3.0.0" 1492 | 1493 | tinycolor2@^1.4.1: 1494 | version "1.4.2" 1495 | resolved "https://registry.npm.taobao.org/tinycolor2/download/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" 1496 | integrity sha1-P2pNEHGtB2dtf6Ry4frECnGdiAM= 1497 | 1498 | to-fast-properties@^2.0.0: 1499 | version "2.0.0" 1500 | resolved "https://registry.nlark.com/to-fast-properties/download/to-fast-properties-2.0.0.tgz?cache=0&sync_timestamp=1628418893613&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fto-fast-properties%2Fdownload%2Fto-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1501 | integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= 1502 | 1503 | to-regex-range@^5.0.1: 1504 | version "5.0.1" 1505 | resolved "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1506 | integrity sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ= 1507 | dependencies: 1508 | is-number "^7.0.0" 1509 | 1510 | toggle-selection@^1.0.6: 1511 | version "1.0.6" 1512 | resolved "https://registry.npm.taobao.org/toggle-selection/download/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" 1513 | integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= 1514 | 1515 | tslib@^1.10.0: 1516 | version "1.14.1" 1517 | resolved "https://registry.nlark.com/tslib/download/tslib-1.14.1.tgz?cache=0&sync_timestamp=1628722556410&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftslib%2Fdownload%2Ftslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 1518 | integrity sha1-zy04vcNKE0vK8QkcQfZhni9nLQA= 1519 | 1520 | tslib@^2.0.3: 1521 | version "2.3.1" 1522 | resolved "https://registry.nlark.com/tslib/download/tslib-2.3.1.tgz?cache=0&sync_timestamp=1628722556410&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftslib%2Fdownload%2Ftslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" 1523 | integrity sha1-6KM1rdXOrlGqJh0ypJAVjvBC7wE= 1524 | 1525 | type-detect@^4.0.8: 1526 | version "4.0.8" 1527 | resolved "https://registry.npm.taobao.org/type-detect/download/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 1528 | integrity sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw= 1529 | 1530 | typescript@^4.1.5: 1531 | version "4.4.3" 1532 | resolved "https://registry.nlark.com/typescript/download/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" 1533 | integrity sha1-vcVAfKorEJ79T4L+EwZW+XeikyQ= 1534 | 1535 | upper-case-first@^2.0.2: 1536 | version "2.0.2" 1537 | resolved "https://registry.npm.taobao.org/upper-case-first/download/upper-case-first-2.0.2.tgz?cache=0&sync_timestamp=1606867326586&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fupper-case-first%2Fdownload%2Fupper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" 1538 | integrity sha1-mSwyc/iCq9GdHgKJTMFHEX+EQyQ= 1539 | dependencies: 1540 | tslib "^2.0.3" 1541 | 1542 | upper-case@^2.0.2: 1543 | version "2.0.2" 1544 | resolved "https://registry.npm.taobao.org/upper-case/download/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a" 1545 | integrity sha1-2JgQgj+qsd8VSbfZenb4Ziuub3o= 1546 | dependencies: 1547 | tslib "^2.0.3" 1548 | 1549 | vite-plugin-babel-import@^2.0.5: 1550 | version "2.0.5" 1551 | resolved "https://registry.nlark.com/vite-plugin-babel-import/download/vite-plugin-babel-import-2.0.5.tgz#733df07dafa9410cae06f820604aebf0b3464d33" 1552 | integrity sha1-cz3wfa+pQQyuBvggYErr8LNGTTM= 1553 | dependencies: 1554 | "@babel/generator" "^7.12.11" 1555 | "@babel/helper-module-imports" "^7.12.5" 1556 | "@babel/parser" "^7.12.11" 1557 | "@babel/traverse" "^7.12.12" 1558 | "@babel/types" "^7.12.12" 1559 | change-case "^4.1.2" 1560 | 1561 | vite-plugin-style-import@^1.2.1: 1562 | version "1.2.1" 1563 | resolved "https://registry.nlark.com/vite-plugin-style-import/download/vite-plugin-style-import-1.2.1.tgz#730c7b0e1f61ed685974fe09d2d5094858491be6" 1564 | integrity sha1-cwx7Dh9h7WhZdP4J0tUJSFhJG+Y= 1565 | dependencies: 1566 | "@rollup/pluginutils" "^4.1.1" 1567 | change-case "^4.1.2" 1568 | debug "^4.3.2" 1569 | es-module-lexer "^0.7.1" 1570 | magic-string "^0.25.7" 1571 | 1572 | vite@^2.0.1: 1573 | version "2.5.10" 1574 | resolved "https://registry.nlark.com/vite/download/vite-2.5.10.tgz?cache=0&sync_timestamp=1632141709201&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fvite%2Fdownload%2Fvite-2.5.10.tgz#c598e3b5a7e1956ffc52eb3b3420d177fc2ed2a5" 1575 | integrity sha1-xZjjtafhlW/8Uus7NCDRd/wu0qU= 1576 | dependencies: 1577 | esbuild "^0.12.17" 1578 | postcss "^8.3.6" 1579 | resolve "^1.20.0" 1580 | rollup "^2.38.5" 1581 | optionalDependencies: 1582 | fsevents "~2.3.2" 1583 | 1584 | warning@^4.0.1: 1585 | version "4.0.3" 1586 | resolved "https://registry.nlark.com/warning/download/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" 1587 | integrity sha1-Fungd+uKhtavfWSqHgX9hbRnjKM= 1588 | dependencies: 1589 | loose-envify "^1.0.0" 1590 | --------------------------------------------------------------------------------