wangzhibo
2026-07-16 b57020623cf0c946706573bf102e548c3a544423
first add
677个文件已添加
79955 ■■■■■ 已修改文件
.cursor/rules/adv-search.mdc 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/crud.mdc 1480 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/form.mdc 1820 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/module.mdc 464 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/search.mdc 743 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/table.mdc 2117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/upsert.mdc 716 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursorrules 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.dockerignore 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.editorconfig 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitattributes 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.hintrc 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.prettierrc.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.vscode/config.code-snippets 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.vscode/crud.code-snippets 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.vscode/extensions.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.vscode/settings.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Dockerfile 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LICENSE 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/cool/eps.d.ts 3756 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/cool/eps.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
env.d.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eslint.config.js 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
nginx.conf 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/.gitignore 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/.prettierrc 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/README.md 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/index.d.ts 811 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/index.html 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/package.json 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/pnpm-lock.yaml 2350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/App.vue 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/add-btn/index.tsx 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/adv/btn.tsx 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/adv/search.tsx 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/context-menu/index.tsx 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/crud/helper.ts 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/crud/index.tsx 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/dialog/index.tsx 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/error-message/index.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/filter/index.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/flex1/index.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form-card/index.tsx 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form-tabs/index.tsx 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/helper/action.ts 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/helper/api.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/helper/index.ts 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/helper/plugins.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/helper/tabs.ts 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/form/index.tsx 683 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/index.tsx 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/multi-delete-btn/index.tsx 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/pagination/index.tsx 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/refresh-btn/index.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/row/index.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/search-key/index.tsx 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/search/helper/plugins.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/search/index.tsx 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/data.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/header.tsx 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/height.ts 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/index.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/op.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/plugins.ts 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/render.tsx 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/row.ts 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/selection.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/helper/sort.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/table/index.tsx 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/components/upsert/index.tsx 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/emitter.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/entry.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/hooks/crud.ts 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/hooks/index.ts 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/locale/en.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/locale/index.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/locale/ja.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/locale/zh-cn.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/locale/zh-tw.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/main.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/provide.ts 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/static/index.scss 862 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/test/service.ts 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/form-hook.ts 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/global.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/index.ts 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/mitt.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/parse.tsx 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/src/utils/vnode.tsx 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/tsconfig.json 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/add-btn/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/adv/btn.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/adv/search.d.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/context-menu/index.d.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/crud/helper.d.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/crud/index.d.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/dialog/index.d.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/error-message/index.d.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/filter/index.d.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/flex1/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form-card/index.d.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form-tabs/index.d.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/helper/action.d.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/helper/api.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/helper/index.d.ts 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/helper/plugins.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/helper/tabs.d.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/form/index.d.ts 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/index.d.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/multi-delete-btn/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/pagination/index.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/refresh-btn/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/row/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/search-key/index.d.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/search/helper/plugins.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/search/index.d.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/data.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/header.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/height.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/index.d.ts 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/op.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/plugins.d.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/render.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/row.d.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/selection.d.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/helper/sort.d.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/table/index.d.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/components/upsert/index.d.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/emitter.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/entry.d.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/hooks/crud.d.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/hooks/index.d.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/locale/en.d.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/locale/index.d.ts 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/locale/ja.d.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/locale/zh-cn.d.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/locale/zh-tw.d.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/main.d.ts 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/provide.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/test/service.d.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/form-hook.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/global.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/index.d.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/mitt.d.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/parse.d.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/types/utils/vnode.d.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/vite.config.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/crud/yarn.lock 1790 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/.eslintrc.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/.prettierrc 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/client.d.ts 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/base.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/config.d.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/ctx/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/demo.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/eps/flatten.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/eps/index.d.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/file/index.d.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/index.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/index.js 2505 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/plugin/index.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/proxy/index.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/svg/index.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/tag/index.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/test.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x.d.ts 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/code.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/color.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/config.d.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/flatten.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/index.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/tailwind.d.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/uniapp-x/utils.d.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/utils/index.d.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/dist/virtual.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/package.json 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/rollup.config.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/base.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/config.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/ctx/index.ts 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/demo.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/eps/index.ts 788 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/file/index.ts 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/index.ts 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/plugin/index.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/proxy/index.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/svg/index.ts 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/tag/index.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/test.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/code.ts 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/config.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/flatten.ts 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/index.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/tailwind.ts 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/uniapp-x/utils.ts 524 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/utils/index.ts 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/src/virtual.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/tsconfig.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/types/index.d.ts 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/vite-plugin/yarn.lock 1771 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 5655 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-workspace.yaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
postcss.config.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/favicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
public/用户导入模版.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/dev.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/index.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/prod.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/proxy.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/bootstrap/eps.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/bootstrap/index.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/bootstrap/module.ts 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/hooks/browser.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/hooks/hmr.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/hooks/index.ts 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/hooks/mitt.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/index.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/module/index.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/router/index.ts 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/service/base.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/service/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/service/request.ts 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/service/stream.ts 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/types/index.ts 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/utils/index.ts 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/utils/loading.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/cool/utils/storage.ts 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/avatar/index.tsx 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/code/json.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/dept/check.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/dept/select.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/editor/index.tsx 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/icon/svg.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/image/index.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/link/index.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/menu/check.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/menu/file.vue 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/menu/icon.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/menu/perms.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/menu/select.vue 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/components/num/index.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/config.ts 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/directives/permission.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/locales/en.json 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/locales/zh-cn.json 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/locales/zh-tw.json 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/401.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/403.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/404.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/500.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/502.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/error/components/error-page.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/login/components/pic-captcha.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/login/index.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/login/static/bg.svg 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/amenu.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/bmenu.tsx 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/global.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/process.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/route-nav.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/slider.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/topbar.vue 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/components/views.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/pages/main/index.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/css/index.scss 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/amount.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/back.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/close-border.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/close.svg 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/delete.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/edit.svg 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/exit.svg 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/expand.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/export.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/fail.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/fold.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/github.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/home.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-activity.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-amount.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-app.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-approve.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-auth.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-ban.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-call.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-camera.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-card.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-cart.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-common.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-component.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-count.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-crown.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-data.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-db.svg 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-delete.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-dept.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-design.svg 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-device.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-dict.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-discover.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-doc.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-download.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-emoji.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-favor.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-file.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-folder.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-goods.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-home.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-hot.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-info.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-iot.svg 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-light.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-like.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-list.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-local.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-log.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-map.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-match.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-menu.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-monitor.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-msg.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-news.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-notice.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-params.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-phone.svg 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-pic.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-question.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-quick.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-rank.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-reward.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-search.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-set.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-tag.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-task.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-time.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-tutorial.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-unlock.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-user.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-video.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-vip.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-warn.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-work.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/icon-workbench.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/image.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/import.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/left.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/my.svg 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/order.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/play.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/plus-border.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/plus.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/refresh.svg 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/right.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/screen-full.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/screen-normal.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/search.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/set.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/sort.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/stats.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/success.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/team.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/static/svg/trend.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/store/app.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/store/index.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/store/menu.ts 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/store/process.ts 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/store/user.ts 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/types/index.d.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/utils/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/utils/permission.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/frame.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/info.vue 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/log.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/menu/components/exp.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/menu/components/imp.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/menu/index.vue 404 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/param.vue 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/role.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/user/components/dept-list.vue 503 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/user/components/user-move.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/views/user/index.vue 342 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/locales/en.json 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/locales/zh-cn.json 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/locales/zh-tw.json 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/driver.vue 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/iot.vue 356 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/site.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/sorter.vue 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/vehicle.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/views/wastetype.vue 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/config.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/directives/color.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/locales/en.json 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/locales/zh-cn.json 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/locales/zh-tw.json 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/adv-search/base.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/adv-search/custom.vue 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/code.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/all.vue 578 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/base.vue 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/dict.vue 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/event.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/select-table.vue 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/service.vue 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/crud/user-select.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/children.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/component/index.vue 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/component/select-labels.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/component/select-status.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/component/select-work.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/component/select-work2.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/config.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/crud.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/disabled.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/event.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/group.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/hidden.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/layout.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/open.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/options.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/plugin/index.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/plugin/role.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/required.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/rules.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/form/setFocus.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/other/context-menu.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/other/tips.vue 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/other/tsx/index.scss 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/other/tsx/index.tsx 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/search/base.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/search/collapse.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/search/custom.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/search/layout.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/search/plugin.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/base.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/children.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/column-custom.vue 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/component/index.vue 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/component/user-info.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/context-menu.vue 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/dict.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/formatter.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/hidden.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/op.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/plugin/base.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/plugin/row-edit.vue 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/plugin/to-tree.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/search.vue 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/selection.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/slot.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/span-method.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/table/summary.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/upsert/base.vue 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/upsert/event.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/upsert/hook/index.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/upsert/hook/reg-pca2.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/components/upsert/mode.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/crud/index.vue 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/category-ratio.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/count-effect.vue 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/count-paid.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/count-user.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/count-views.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/hot-goods.vue 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/components/tab-chart.vue 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/home/index.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/tenant.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/demo/views/test/route.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/config.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/locales/en.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/locales/zh-cn.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/locales/zh-tw.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/store/dict.ts 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/store/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/types/index.d.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/utils/index.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/dict/views/list.vue 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/components/ai-code/btn.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/components/ai-code/dev.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/components/auto-menu.vue 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/components/auto-perms.vue 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/config.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/hooks/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/hooks/menu.ts 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/hooks/plugin.ts 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/locales/en.json 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/locales/zh-cn.json 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/locales/zh-tw.json 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/static/index.scss 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/static/svg/icon-vue.svg 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/types/index.d.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/views/ai-code.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/helper/views/plugins.vue 1033 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/locales/en.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/locales/zh-cn.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/locales/zh-tw.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/views/auditlog.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/views/cameragps.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/views/siteweight.vue 242 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/views/vehiclegps.vue 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/views/weight.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/recycle/locales/en.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/recycle/locales/zh-cn.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/recycle/locales/zh-tw.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/recycle/views/data.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/components/space-inner.vue 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/components/space.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/config.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/hooks/index.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/locales/en.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/locales/zh-cn.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/locales/zh-tw.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/space/views/list.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/task/components/logs.vue 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/task/locales/en.json 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/task/locales/zh-cn.json 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/task/locales/zh-tw.json 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/task/views/list.vue 499 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/components/user-select.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/config.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/locales/en.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/locales/zh-cn.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/locales/zh-tw.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/views/list.vue 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/comm/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/column-custom/index.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/date/picker.vue 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/date/text.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/dict/index.tsx 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/number/range.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/render/index.tsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/select/button.tsx 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/select/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/select/index.tsx 275 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/select/table.vue 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/switch/index.tsx 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/text/index.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/components/user/select.vue 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/config.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/locales/en.json 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/locales/zh-cn.json 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/locales/zh-tw.json 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/form/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/form/setFocus.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/search/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/search/setAuto.ts 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/table/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/table/rowEdit.tsx 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/crud/plugins/table/toTree.ts 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/account.vue 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/dict.vue 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/doc.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/eps.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/index.vue 297 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/components/proxy.vue 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/config.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/locales/en.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/locales/zh-cn.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/locales/zh-tw.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/cool.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/echarts.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/element-plus.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/lodash.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/pinia.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/tailwindcss.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/vite.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/static/vue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/dev-tools/utils/index.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/distpicker/components/index.tsx 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/distpicker/config.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/distpicker/data/pca.json 5300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/distpicker/demo/base.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/echarts/config.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/components/preview.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/config.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/demo/base.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/locales/en.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/locales/zh-cn.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-preview/locales/zh-tw.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/components/wang.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/config.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/demo/base.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/locales/en.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/locales/zh-cn.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/editor-wang/locales/zh-tw.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/element-ui/config.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/element-ui/css/index.scss 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/components/export-btn.tsx 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/components/import-btn.vue 413 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/config.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/demo/base.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/locales/en.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/locales/zh-cn.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/locales/zh-tw.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/excel/utils/index.ts 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/github/components/code.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/github/config.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/i18n/components/switch.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/i18n/config.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/i18n/index.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/i18n/static/svg/lang.svg 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/iconfont/config.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/iconfont/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/iconfont/utils/index.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tailwind/config.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tailwind/static/index.css 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/components/theme.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/config.ts 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/hooks/index.ts 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/locales/en.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/locales/zh-cn.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/locales/zh-tw.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/static/css/index.scss 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/static/svg/dark.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/static/svg/light.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/static/svg/theme.svg 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/types/index.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/theme/utils/index.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/components/upload-item/index.vue 445 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/components/upload-item/viewer.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/components/upload.vue 642 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/config.ts 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/base.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/check.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/custom.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/drag.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/file.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/multiple.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/small.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/demo/space.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/hooks/index.ts 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/locales/en.json 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/locales/zh-cn.json 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/locales/zh-tw.json 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/audio.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/excel.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/file.svg 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/image.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/pdf.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/ppt.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/rar.svg 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/video.svg 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/static/svg/word.svg 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/types/index.d.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/upload/utils/index.ts 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/components/group.vue 701 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/components/head.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/config.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/demo/group.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/demo/head.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/hooks/group.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/hooks/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/locales/en.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/locales/zh-cn.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/locales/zh-tw.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/view/types/index.d.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/shims-vue.d.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tailwind.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/adv-search.mdc
New file
@@ -0,0 +1,305 @@
---
description: cl-adv-search 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>base</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['adv-search/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="起步" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!--【很重要】高级搜索组件按钮 -->
                        <cl-adv-btn />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】高级搜索组件 -->
                    <cl-adv-search ref="AdvSearch" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-adv-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const AdvSearch = useAdvSearch({
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 自定义 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>custom</el-tag>
            <span>自定义</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['adv-search/custom.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="自定义" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!--【很重要】高级搜索组件按钮 -->
                        <cl-adv-btn>更多搜索</cl-adv-btn>
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】高级搜索组件 -->
                    <cl-adv-search ref="AdvSearch">
                        <!-- 自定义按钮 -->
                        <template #slot-btn>
                            <el-button @click="toSearch">自定义</el-button>
                        </template>
                    </cl-adv-search>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-adv-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const AdvSearch = useAdvSearch({
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ],
    title: '更多搜索',
    size: '50%',
    op: ['close', 'search', 'slot-btn']
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
// 自定义搜索
function toSearch() {
    refresh({ page: 1 });
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
.cursor/rules/crud.mdc
New file
@@ -0,0 +1,1480 @@
---
description: cl-crud 组件示例
globs: *.tsx, *.ts, *.vue
---
## 完整示例 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>all</el-tag>
            <span>完整示例</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/all.vue']" />
            <cl-dialog v-model="visible" title="完整示例" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 刷新按钮 -->
                        <cl-refresh-btn />
                        <!-- 新增按钮 -->
                        <cl-add-btn />
                        <!-- 批量删除按钮 -->
                        <cl-multi-delete-btn />
                        <!-- 筛选 -->
                        <cl-filter label="状态筛选">
                            <!-- 配置prop,选择后会自动过滤列表 -->
                            <cl-select :options="options.status" prop="status" :width="120" />
                        </cl-filter>
                        <!-- 字典 -->
                        <cl-filter label="工作(字典)">
                            <cl-select
                                tree
                                :options="dict.get('occupation')"
                                prop="occupation"
                                :width="140"
                                check-strictly
                            />
                        </cl-filter>
                        <cl-flex1 />
                        <!-- 导入 -->
                        <cl-import-btn template="/用户导入模版.xlsx" />
                        <!-- 导出 -->
                        <cl-export-btn :columns="Table?.columns" />
                        <!-- 自定义列 -->
                        <cl-column-custom
                            :ref="setRefs('columnCustom')"
                            :columns="Table?.columns"
                        />
                        <!-- 关键字搜索 -->
                        <cl-search-key placeholder="搜索姓名、手机号" :width="250" />
                        <!-- 高级搜索按钮 -->
                        <cl-adv-btn />
                    </cl-row>
                    <cl-row>
                        <!-- 表格 -->
                        <cl-table
                            ref="Table"
                            show-summary
                            :summary-method="onSummaryMethod"
                            :auto-height="false"
                        >
                            <!-- 展开信息 -->
                            <template #column-detail="{ scope }">
                                <div style="padding: 0 10px">
                                    <el-descriptions border :column="3">
                                        <el-descriptions-item label="ID">
                                            {{ scope.row.id }}
                                        </el-descriptions-item>
                                        <el-descriptions-item label="姓名">
                                            {{ scope.row.name }}
                                        </el-descriptions-item>
                                        <el-descriptions-item label="存款">
                                            {{ scope.row.wages }}
                                        </el-descriptions-item>
                                        <el-descriptions-item label="出生年月">
                                            {{ scope.row.createTime }}
                                        </el-descriptions-item>
                                    </el-descriptions>
                                </div>
                            </template>
                            <!-- 自定义列 -->
                            <template #column-wages="{ scope }">
                                <span>{{ scope.row.wages }}🤑</span>
                            </template>
                        </cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <!-- 分页 -->
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                    <!-- 高级搜索 -->
                    <cl-adv-search ref="AdvSearch" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script lang="tsx" setup>
defineOptions({
    name: 'demo-crud'
});
import { useCrud, useUpsert, useTable, useAdvSearch, useSearch } from '@cool-vue/crud';
import { useDict } from '/$/dict';
import { reactive, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useCool } from '/@/cool';
// 基础
const { service, refs, setRefs } = useCool();
// 字典
const { dict } = useDict();
// 选项,统一命名options,存放所有的下拉等其他选项列表数据
const options = reactive({
    status: [
        {
            label: '启用',
            value: 1
        },
        {
            label: '禁用',
            type: 'danger',
            value: 0
        }
    ]
});
// 合计数据
const subData = reactive({
    wages: 0
});
// crud
const Crud = useCrud(
    {
        // 绑定的服务,如:service.demo.goods、service.base.sys.user
        service: 'test',
        // 刷新事件
        async onRefresh(params, { next }) {
            const res = await next(params);
            Object.assign(subData, res.subData);
        }
    },
    app => {
        // Crud 加载完,默认刷新一次
        app.refresh({
            size: 10
            // status: 1 // 带额外参数的请求
        });
    }
);
// 刷新列表,统一调用这个方法去刷新
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
// 新增、编辑
// 插入类型 <Eps.UserInfoEntity>,prop 和 data 会有提示
const Upsert = useUpsert<Eps.UserInfoEntity>({
    items: [
        // 分组
        {
            type: 'tabs',
            props: {
                type: 'card',
                labels: [
                    {
                        label: '基础信息',
                        value: 'base'
                    },
                    {
                        label: '其他配置',
                        value: 'other'
                    }
                ]
            }
        },
        {
            label: '头像',
            prop: 'avatarUrl',
            group: 'base',
            component: {
                name: 'cl-upload'
            }
        },
        {
            label: '账号',
            group: 'base',
            prop: 'account',
            component: {
                name: 'el-input'
            }
        },
        // 动态配置,新增显示、编辑隐藏
        () => {
            return () => {
                return {
                    label: '密码',
                    group: 'base',
                    prop: 'password',
                    hidden: Upsert.value?.mode == 'update', // 通过 mode 参数判断
                    component: {
                        name: 'el-input',
                        props: {
                            type: 'password'
                        }
                    }
                };
            };
        },
        {
            group: 'base',
            prop: 'user',
            component: {
                name: 'cl-form-card',
                props: {
                    label: '用户信息(多层级展示)'
                }
            },
            children: [
                {
                    label: '姓名',
                    prop: 'name',
                    required: true,
                    component: {
                        name: 'el-input'
                    }
                },
                {
                    label: '存款',
                    prop: 'wages',
                    component: {
                        name: 'el-input-number'
                    }
                }
            ]
        },
        {
            group: 'base',
            prop: 'contact',
            component: {
                name: 'cl-form-card',
                props: {
                    label: '联系信息',
                    expand: false
                }
            },
            children: [
                {
                    label: '手机号',
                    prop: 'phone',
                    component: {
                        name: 'el-input'
                    }
                },
                {
                    label: '省市区',
                    prop: 'pca',
                    group: 'base',
                    component: {
                        name: 'cl-distpicker'
                    }
                }
            ]
        },
        {
            group: 'other',
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'el-tree-select',
                props: {
                    data: dict.get('occupation'),
                    checkStrictly: true
                }
            }
        },
        {
            group: 'other',
            label: '身份证照片',
            prop: 'idCardPic',
            component: {
                name: 'cl-upload',
                props: {
                    isSpace: true,
                    size: [200, 300]
                }
            }
        }
    ],
    // 详情钩子
    onInfo(data, { next, done }) {
        // 继续请求 info 接口,可以带其他自定义参数
        // next({
        //     id: data.id,
        //    status: 1
        // });
        // 使用其他接口
        // service.demo.goods.info({ id: data.id }).then((res) => {
        //     done(res);
        // });
        // 直接取列表的数据返回
        done(data);
    },
    // 提交钩子
    onSubmit(data, { next, close, done }) {
        console.log('onSubmit', data);
        // 继续请求 update/add 接口
        next(data);
        // 自定义接口
        // service.demo.goods
        //     .update(data)
        //     .then(() => {
        //         ElMessage.success("保存成功");
        //         // 操作完,刷新列表
        //         refresh();
        //         // 关闭窗口
        //         close();
        //     })
        //     .catch((err) => {
        //         ElMessage.error(err.message);
        //         // 关闭加载状态
        //         done();
        //     });
    },
    // 打开后,数据加载完,onInfo 之后
    onOpened(data) {
        if (Upsert.value?.mode != 'info') {
            ElMessage.info('编辑中');
        }
    },
    // 关闭钩子
    onClose(action, done) {
        if (Upsert.value?.mode == 'update') {
            if (action == 'close') {
                return ElMessageBox.confirm('还没填完,确定关闭不?', '提示', {
                    type: 'warning'
                })
                    .then(() => {
                        done();
                        ElMessage.info('好吧');
                    })
                    .catch(() => {
                        ElMessage.success('请继续编辑');
                    });
            }
        }
        done();
    }
});
// 表格
const Table = useTable({
    columns: [
        {
            type: 'selection',
            width: 60
        },
        // 展开列
        {
            label: '展开',
            type: 'expand',
            prop: 'detail',
            width: 60
        },
        {
            label: '头像',
            prop: 'avatar',
            width: 100,
            component: {
                name: 'cl-image',
                props: {
                    size: 40
                }
            }
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 120
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140,
            // 带搜索组件
            search: {
                component: {
                    name: 'el-input',
                    props: {
                        placeholder: '搜索手机号'
                    }
                }
            }
        },
        {
            label: '账号',
            prop: 'account',
            minWidth: 150
        },
        {
            label: '存款(元)',
            prop: 'wages',
            minWidth: 150,
            sortable: 'desc' // 默认倒序
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            dictColor: true,
            minWidth: 150,
            dictAllLevels: true, // 显示所有等级
            // 带搜索组件
            search: {
                component: {
                    name: 'cl-select',
                    props: {
                        options: dict.get('occupation')
                    }
                }
            }
        },
        {
            label: '状态',
            orderNum: 2,
            prop: 'status',
            minWidth: 100,
            component: {
                name: 'cl-switch'
            }
        },
        {
            label: '出生年月',
            orderNum: 1,
            minWidth: 165,
            prop: 'createTime',
            sortable: 'custom',
            search: {
                component: {
                    name: 'cl-date-picker',
                    props: {
                        type: 'date',
                        valueFormat: 'YYYY-MM-DD',
                        placeholder: '搜索日期'
                    }
                }
            }
        },
        {
            type: 'op',
            width: 340,
            // 静态配置按钮
            // buttons: ["info", "edit", "delete"],
            // 动态配置按钮
            buttons({ scope }) {
                return [
                    'info',
                    'edit',
                    'delete',
                    {
                        label: '自定义',
                        onClick() {
                            ElMessage.info(`他是:${scope.row.name}`);
                        }
                    }
                ];
            }
        }
    ]
});
// 合计
function onSummaryMethod() {
    // 添加自定义列组件后
    return ['合计', '', ...refs.columnCustom.summary(subData)];
}
// 高级搜索
const AdvSearch = useAdvSearch({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            hook: {
                bind: 'string'
            },
            component: {
                name: 'el-select',
                options: dict.get('occupation')
            }
        }
    ]
});
// 搜索
const Search = useSearch({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>base</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="起步" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-refresh-btn />
                        <cl-add-btn />
                        <cl-multi-delete-btn />
                        <cl-flex1 />
                        <cl-search-key />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';
const { service } = useCool();
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        // test 为测试数据模式,详细说明移步到 service 例子
        service: 'test'
    },
    app => {
        //【很重要】首次请求,数据一并添加到请求参数中
        app.refresh({
            size: 10,
            status: 1
        });
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 修改文案 / 接口 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>dict</el-tag>
            <span>修改文案 / 接口</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/dict.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="修改文案 / 接口" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-refresh-btn />
                        <cl-add-btn />
                        <cl-multi-delete-btn />
                        <cl-flex1 />
                        <cl-search-key />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        //【很重要】配置 service,如:service.base.sys.user
        service: 'test',
        //【很重要】字典配置,文案和请求方法等
        dict: {
            // 修改请求
            // 比如说默认列表请求的是 page 接口,可以修改成 getUserList 等等,这取决于后端有没有这个接口。
            api: {
                list: 'list',
                add: 'add',
                update: 'update',
                delete: 'delete',
                info: 'info',
                page: 'page'
            },
            // 修改文案
            label: {
                op: '操作',
                add: '添加',
                delete: '移除',
                multiDelete: '批量移除',
                update: '修改',
                refresh: '刷新',
                info: '详情'
            }
        }
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 事件监听 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>event</el-tag>
            <span>事件监听</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/event.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="事件监听" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-refresh-btn />
                        <cl-add-btn />
                        <cl-multi-delete-btn />
                        <cl-flex1 />
                        <cl-search-key />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table">
                            <!-- 自定义按钮 -->
                            <template #slot-btn="{ scope }">
                                <el-button @click="onEvent(scope.row)">自定义事件</el-button>
                            </template>
                        </cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';
import { ElMessage } from 'element-plus';
const { service } = useCool();
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        // 配置 service
        service: 'test',
        //【很重要】监听刷新事件,每次调用 Crud.value.refresh() 会触发
        onRefresh(params, { next }) {
            // 默认使用 next(params),也可以自己对数据进行处理
            next({
                ...params,
                status: 1
            });
        },
        // 监听删除事件,点击删除按钮触发
        onDelete(selection, { next }) {
            // 传入 ids,批量删除多个数据
            next({
                ids: selection.map(e => e.id)
            });
        }
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            width: 300,
            buttons: ['edit', 'delete', 'slot-btn']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
// 调用 Crud 方法
function onEvent(row: any) {
    ElMessage.info('自定义打开新增');
    // 打开新增表单
    Crud.value?.rowAdd();
    // 打开编辑表单
    // Crud.value?.rowEdit(row);
    // 打开删除提示框
    // Crud.value?.rowDelete(row);
    // 获取已请求的参数
    // Crud.value?.getParams();
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 选择表格 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>select-table</el-tag>
            <span>选择表格</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/select-table.vue']" />
            <!-- 自定义表格组件 -->
            <cl-form ref="Form" />
        </div>
        <div class="f">
            <span class="date">2025-02-07</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { useCool } from '/@/cool';
import UserSelect from '/$/user/components/user-select.vue';
const { service } = useCool();
const Form = useForm();
const columns = [
    {
        label: '头像',
        prop: 'headImg',
        component: {
            name: 'cl-avatar'
        }
    },
    {
        label: '昵称',
        prop: 'nickName'
    },
    {
        label: '创建时间',
        prop: 'createTime'
    }
];
function open() {
    Form.value?.open({
        width: '800px',
        title: '选择表格',
        items: [
            {
                label: '多选 - default',
                prop: 'a',
                value: [],
                component: {
                    vm: UserSelect,
                    props: {
                        multiple: true
                    }
                },
                span: 12
            },
            {
                label: '单选 - default',
                prop: 'b',
                component: {
                    name: 'cl-select-table',
                    props: {
                        pickerType: 'default',
                        multiple: false,
                        columns,
                        service: service.base.sys.user
                    }
                },
                span: 12
            },
            {
                label: '多选 - text',
                prop: 'c',
                value: [],
                component: {
                    name: 'cl-select-table',
                    props: {
                        pickerType: 'text',
                        multiple: true,
                        columns,
                        service: service.base.sys.user
                    }
                },
                span: 12
            },
            {
                label: '单选 - text',
                prop: 'd',
                component: {
                    name: 'cl-select-table',
                    props: {
                        pickerType: 'text',
                        multiple: false,
                        columns,
                        service: service.base.sys.user
                    }
                },
                span: 12
            },
            {
                label: '多选 - table',
                prop: 'e',
                value: [],
                component: {
                    name: 'cl-select-table',
                    props: {
                        pickerType: 'table',
                        multiple: true,
                        columns,
                        service: service.base.sys.user
                    }
                }
            }
        ]
    });
}
</script>
```
## Service 配置 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>service</el-tag>
            <span>Service 配置</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/service.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="Service 配置" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-refresh-btn />
                        <cl-add-btn />
                        <cl-multi-delete-btn />
                        <cl-flex1 />
                        <cl-search-key />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useCool } from '/@/cool';
import { useDict } from '/$/dict';
//【很重要】service 是所有请求的集合,是一个对象(刷新页面和保存代码会自动读取后端的所有接口)
const { service, route } = useCool();
console.log('service', service);
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        //【很重要】配置 service,如:service.base.sys.user
        // 不需要到具体的方法,如:service.base.sys.user.page,这是错误的!
        // 实际用法
        // service: service.base.sys.user,
        // 测试示例
        service: 'test'
        // 自定义配置1,添加本地 service 文件。
        // 【很重要】参考 /src/modules/demo/service/test.ts
        // 【很重要】必须放在目录 modules/*/service/ 下,才会自动注入到 service 中
        // service: service.test
        // 自定义配置2,针对一些特殊场景
        // service: {
        //     page(params: any) {
        //         // params 请求参数
        //         //【很重要】必须返回一个 Promise 格式
        //         return Promise.resolve({
        //             list: [],
        //             pagination: {
        //                 total: 1,
        //                 page: 1,
        //                 size: 20
        //             }
        //         });
        //     }
        //     // add、delete、update、info、list 也是如此配置
        // }
    },
    app => {
        // 首次调用刷新接口。在弹窗的情况下可以注释,用 Crud.value?.refresh() 方式手动调用
        app.refresh();
        // 带参数
        // app.refresh({
        //     userId: route.query.id
        // });
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 选择成员 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>user-select</el-tag>
            <span>选择成员</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['crud/user-select.vue']" />
            <!-- 自定义表格组件 -->
            <cl-form ref="Form" />
        </div>
        <div class="f">
            <span class="date">2025-02-07</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '选择成员',
        items: [
            {
                label: '单选',
                prop: 'userId',
                component: {
                    name: 'cl-user-select',
                    props: {
                        multiple: false,
                        onChange(val) {
                            console.log(val);
                        }
                    }
                },
                required: true
            },
            {
                label: '多选',
                prop: 'userIds',
                component: {
                    name: 'cl-user-select',
                    props: {
                        multiple: true,
                        onChange(val) {
                            console.log(val);
                        }
                    }
                },
                required: true
            }
        ]
    });
}
</script>
```
.cursor/rules/form.mdc
New file
@@ -0,0 +1,1820 @@
---
description: cl-form 组件示例
globs: *.tsx, *.ts, *.vue
---
## 层级显示 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>children</el-tag>
            <span>层级显示</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/children.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '层级显示',
        items: [
            {
                label: '姓名',
                prop: 'name',
                component: {
                    name: 'el-input'
                }
            },
            {
                label: '年龄',
                prop: 'age',
                value: 18,
                component: {
                    name: 'el-input-number'
                }
            },
            // 基础信息
            {
                component: {
                    //【很重要】使用 cl-form-card 组件渲染,也可以使用自定义
                    name: 'cl-form-card',
                    props: {
                        // 标题
                        label: '基础信息',
                        // 是否展开,默认 true
                        expand: true
                    }
                },
                children: [
                    {
                        label: '账号',
                        prop: 'account',
                        component: {
                            name: 'el-input'
                        }
                    },
                    {
                        label: '密码',
                        prop: 'password',
                        component: {
                            name: 'el-input'
                        }
                    }
                ]
            },
            // 其他信息
            {
                component: {
                    name: 'cl-form-card',
                    props: {
                        label: '其他信息',
                        expand: false
                    }
                },
                children: [
                    {
                        label: '身份证',
                        prop: 'idcard',
                        component: {
                            name: 'el-input'
                        }
                    },
                    {
                        label: '学校',
                        prop: 'school',
                        component: {
                            name: 'el-input'
                        }
                    },
                    {
                        label: '专业',
                        prop: 'major',
                        component: {
                            name: 'el-input'
                        }
                    }
                ]
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 组件渲染 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>component</el-tag>
            <span>组件渲染</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code
                :files="[
                    'form/component/index.vue',
                    'form/component/select-labels.vue',
                    'form/component/select-status.vue',
                    'form/component/select-work.vue',
                    'form/component/select-work2.vue'
                ]"
            />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form">
                <!-- 年龄插槽 -->
                <template #slot-age="{ scope }">
                    <!-- scope 为表单值 -->
                    <el-input-number v-model="scope.age" :min="18" :max="100"></el-input-number>
                </template>
            </cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { ElMessage } from 'element-plus';
import SelectWork from './select-work2.vue';
import SelectLabels from './select-labels.vue';
import SelectStatus from './select-status.vue';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '组件配置',
        items: [
            {
                label: '昵称',
                prop: 'name',
                // 组件配置方式1:标签名(方便,但是不建议组件全局注册)
                value: '神仙',
                component: {
                    // 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
                    name: 'el-input'
                }
            },
            {
                label: '手机号',
                prop: 'phone',
                value: '13255022000',
                component: {
                    name: 'el-input',
                    // 自定义插槽
                    slots: {
                        prepend() {
                            return '+86';
                        }
                    }
                }
            },
            {
                label: '年龄',
                prop: 'age',
                // 组件配置方式2:插槽(万能,就是代码多写点)
                value: 18,
                component: {
                    // 必须是 "slot-" 开头
                    name: 'slot-age'
                }
            },
            // -- start 组件配置方式3:组件实例(不想全局注册,但又想组件化)
            {
                label: '工作',
                prop: 'work',
                value: '设计',
                component: {
                    // 双向绑定
                    vm: SelectWork
                }
            },
            {
                label: '标签',
                prop: 'labels',
                value: ['多金', '深情'],
                component: {
                    // scope[prop]绑定
                    vm: SelectLabels
                }
            },
            {
                label: '状态',
                prop: 'status',
                value: 1,
                component: {
                    // useForm 绑定
                    vm: SelectStatus
                }
            }
            // -- end
        ],
        on: {
            submit(data, { close }) {
                ElMessage.info(
                    `${data.name || '无名'}(${data.age || 18}岁)工作:${data.work || '无'}`
                );
                close();
            }
        }
    });
}
</script>
```
## select-labels 示例
```vue
<template>
    <!--【很重要】直接绑定表单值 scope[prop] -->
    <!-- !符号,只是为了类型提示不错误 -->
    <el-select v-model="scope[prop!]" multiple>
        <el-option
            v-for="(item, index) in list"
            :key="index"
            :label="item.label"
            :value="item.label"
        />
    </el-select>
</template>
<!--【很重要】必须要有name,避免注册后和其他冲突 -->
<script setup lang="ts">
defineOptions({
    name: 'select-labels'
});
import { ref } from 'vue';
const props = defineProps({
    scope: null, // 表单值
    prop: String // 表单项配置的 prop
});
// 选项列表
const list = ref<{ label: string; value: string }[]>([
    {
        label: '帅气',
        value: '帅气' // 测试直接使用label,真实情况可能是1,2,3,4或者id
    },
    {
        label: '多金',
        value: '多金'
    },
    {
        label: '深情',
        value: '深情'
    }
]);
</script>
```
## select-status 示例
```vue
<template>
    <!--【很重要】直接绑定status,或者使用 form[prop!] -->
    <el-radio-group v-model="form.status">
        <el-radio v-for="(item, index) in list" :key="index" :value="item.value">
            {{ item.label }}
        </el-radio>
    </el-radio-group>
</template>
<!--【很重要】必须要有name,避免注册后和其他冲突 -->
<script setup lang="ts">
defineOptions({
    name: 'select-status'
});
import { useForm } from '@cool-vue/crud';
import { computed, ref } from 'vue';
const props = defineProps({
    scope: null, // 表单值
    prop: String // 表单项配置的 prop
});
// 使用 useForm,能直接获取到上级的表单实例,
// 比如操作表单的 Form.value?.submit、Form.value?.close等
// 获取表单值,Form.value?.form
const Form = useForm();
// 表单值,包一层不会太难受
const form = computed(() => Form.value?.form || {});
// 选项列表
const list = ref<{ label: string; value: number }[]>([
    {
        label: '很好',
        value: 1
    },
    {
        label: '不舒服',
        value: 2
    },
    {
        label: '要嘎了',
        value: 3
    }
]);
</script>
```
## select-work 示例
```vue
<template>
    <el-select v-model="active" @change="onChange">
        <el-option
            v-for="(item, index) in list"
            :key="index"
            :label="item.label"
            :value="item.label"
        />
    </el-select>
</template>
<!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
<script setup lang="ts">
defineOptions({
    name: 'select-work'
});
import { ref, watch } from 'vue';
const props = defineProps({
    modelValue: String
});
const emit = defineEmits(['update:modelValue', 'change']);
//【很重要】绑定值
// 这种方式虽然麻烦,但是可扩展性高,一些复杂的数据结构可以按这种方式绑定值
const active = ref();
// 选项列表
const list = ref<{ label: string; value: string }[]>([
    {
        label: '倒茶',
        value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
    },
    {
        label: '设计',
        value: '设计'
    },
    {
        label: '开发',
        value: '开发'
    }
]);
//【很重要】更新绑定值,表单提交才能得到选择后的
function onChange(val: string) {
    emit('update:modelValue', val);
    emit('change', val);
}
//【很重要】使用监听的方式,避免表单打开数据是异步获取的情况
watch(
    () => props.modelValue,
    val => {
        // 设置选中的值
        active.value = val;
    },
    {
        immediate: true
    }
);
</script>
```
## select-work2 示例
```vue
<template>
    <el-select v-model="active">
        <el-option
            v-for="(item, index) in list"
            :key="index"
            :label="item.label"
            :value="item.label"
        />
    </el-select>
</template>
<!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
<script setup lang="ts">
defineOptions({
    name: 'select-work2'
});
import { ref, useModel } from 'vue';
const props = defineProps({
    modelValue: String
});
//【很重要】绑定值,使用 useModel 的方式双向绑定
const active = useModel(props, 'modelValue');
// 选项列表
const list = ref<{ label: string; value: string }[]>([
    {
        label: '倒茶',
        value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
    },
    {
        label: '设计',
        value: '设计'
    },
    {
        label: '开发',
        value: '开发'
    }
]);
</script>
```
## 参数配置 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>config</el-tag>
            <span>参数配置</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/config.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form">
                <!-- 按钮插槽 -->
                <template #slot-btns>
                    <el-button type="danger">按钮插槽</el-button>
                </template>
            </cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { ElMessage } from 'element-plus';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '参数配置',
        // 打开是否重置表单
        isReset: false,
        // 默认表单值
        form: {
            nickName: '神仙都没用'
        },
        // 表单配置
        props: {
            // 标签宽度
            labelWidth: '120px',
            // 标签位置
            labelPosition: 'top'
        },
        // 窗口的高。配置后,在窗口内部滚动。默认整个页面滚动
        height: '60vh',
        // 窗口的宽,默认 50%
        width: '60%',
        // 窗口设置
        dialog: {
            // 是否隐藏头部
            hideHeader: false,
            // 顶部操作按钮,默认["fullscreen", "close"]
            // fullscreen 全屏
            // close 关闭
            controls: ['close']
        },
        // 底部操作按钮
        op: {
            // 默认靠右布局
            justify: 'flex-end',
            // 保存按钮文字
            saveButtonText: '提交',
            // 关闭按钮文字
            closeButtonText: '关闭',
            // 是否隐藏
            hidden: false,
            // 按钮配置
            buttons: [
                // 自定义
                {
                    label: '自定义按钮',
                    onClick() {
                        ElMessage.success('自定义按钮点击');
                    }
                },
                // close 关闭
                'close',
                // save 保存
                'save',
                // 插槽使用,配合 template,往上看 cl-form 组件
                'slot-btns'
            ]
        },
        // 表单项配置
        items: [
            {
                label: '昵称',
                prop: 'nickName',
                component: {
                    name: 'el-input'
                }
            }
        ],
        // 事件
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 内嵌CRUD 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>crud</el-tag>
            <span>内嵌CRUD</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/crud.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form">
                <template #slot-crud>
                    <cl-crud ref="Crud" border>
                        <cl-row>
                            <!-- 刷新按钮 -->
                            <cl-refresh-btn />
                            <!-- 新增按钮 -->
                            <cl-add-btn />
                            <!-- 删除按钮 -->
                            <cl-multi-delete-btn />
                            <cl-flex1 />
                            <!-- 关键字搜索 -->
                            <cl-search-key placeholder="搜索姓名、手机号" />
                        </cl-row>
                        <cl-row>
                            <!-- 数据表格 -->
                            <cl-table ref="Table" />
                        </cl-row>
                        <cl-row>
                            <cl-flex1 />
                            <!-- 分页控件 -->
                            <cl-pagination />
                        </cl-row>
                        <!-- 新增、编辑 -->
                        <cl-upsert ref="Upsert" />
                    </cl-crud>
                </template>
            </cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useForm, useTable, useUpsert } from '@cool-vue/crud';
// cl-upsert
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '创建时间',
            prop: 'createTime',
            component: {
                name: 'el-date-picker'
            }
        }
    ]
});
// cl-table
const Table = useTable({
    autoHeight: false,
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            type: 'op'
        }
    ]
});
// cl-crud
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh({
            size: 10
        });
    }
);
const Form = useForm();
function open() {
    Form.value?.open({
        title: '内嵌CRUD',
        props: {
            labelPosition: 'top'
        },
        dialog: {
            height: '70vh',
            width: '1000px'
        },
        items: [
            {
                label: '姓名',
                prop: 'name',
                component: {
                    name: 'el-input',
                    props: {
                        placeholder: '请填写姓名'
                    }
                },
                rules: {
                    required: true,
                    message: '姓名不能为空'
                }
            },
            {
                label: '内嵌 cl-crud',
                component: {
                    name: 'slot-crud'
                }
            }
        ],
        on: {
            submit() {
                Form.value?.close();
            }
        }
    });
}
</script>
```
## 组件禁用 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>disabled</el-tag>
            <span>组件禁用</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/disabled.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '组件禁用',
        items: [
            {
                label: '账号',
                prop: 'account',
                component: {
                    name: 'el-input',
                    props: {
                        // 设置 boolean 值控制组件的禁用状态(前提是组件支持这个参数,element 的组件几乎都有)
                        disabled: true
                    }
                }
            },
            {
                label: '密码',
                prop: 'password',
                component: {
                    name: 'el-input'
                }
            }
        ],
        on: {
            open() {
                // 通用 setProps 方法去设置 disabled, 1.5s后禁用
                setTimeout(() => {
                    Form.value?.setProps('password', { disabled: true });
                }, 1500);
            },
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 组件事件 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>event</el-tag>
            <span>组件事件</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/event.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { ElMessage } from 'element-plus';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '组件事件',
        items: [
            {
                label: '账号',
                prop: 'account',
                component: {
                    name: 'el-input',
                    props: {
                        // 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
                        // 前提是组件内有触发事件
                        onBlur() {
                            ElMessage.info('账号检查中');
                        }
                    }
                }
            },
            {
                label: '是否实名',
                prop: 'status',
                value: 1,
                component: {
                    name: 'el-radio-group',
                    options: [
                        {
                            label: '关闭',
                            value: 0
                        },
                        {
                            label: '开启',
                            value: 1
                        }
                    ],
                    props: {
                        // 值改变事件
                        onChange(val: number) {
                            if (val == 1) {
                                // 显示表单项
                                Form.value?.showItem('idcard');
                            } else {
                                // 隐藏表单项
                                Form.value?.hideItem('idcard');
                                // 清空值
                                Form.value?.setForm('idcard', undefined);
                            }
                        }
                    }
                }
            },
            {
                label: '身份证',
                prop: 'idcard',
                component: {
                    name: 'el-input'
                }
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 分组显示 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>group</el-tag>
            <span>分组显示</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/group.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '分组显示',
        items: [
            {
                //【很重要】必须为 tabs
                type: 'tabs',
                props: {
                    // 分组样式
                    type: 'card',
                    // 分组列表,必须是 { label, value } 的数组格式
                    labels: [
                        {
                            label: '基础信息', // 标题
                            value: 'base' // 唯一标识
                        },
                        {
                            label: '认证信息',
                            value: 'auth'
                        }
                    ]
                }
            },
            // 基础信息
            {
                group: 'base', // 标识
                label: '账号',
                prop: 'account',
                required: true,
                component: {
                    name: 'el-input'
                }
            },
            {
                group: 'base', // 标识
                label: '密码',
                prop: 'password',
                required: true,
                component: {
                    name: 'el-input'
                }
            },
            // 其他信息 group = other
            {
                group: 'auth', // 标识
                label: '身份证',
                prop: 'idcard',
                required: true,
                component: {
                    name: 'el-input'
                }
            },
            {
                group: 'auth', // 标识
                label: '学校',
                prop: 'school',
                component: {
                    name: 'el-input'
                }
            },
            {
                group: 'auth', // 标识
                label: '专业',
                prop: 'major',
                component: {
                    name: 'el-input'
                }
            }
        ],
        on: {
            //【提示】当第一组验证通过后,会自动切换到下一组展示,直到全部通过才可提交
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 隐藏/显示 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>hidden</el-tag>
            <span>隐藏/显示</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/hidden.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '隐藏/显示',
        items: [
            {
                label: '状态',
                prop: 'status',
                value: 0,
                component: {
                    name: 'el-radio-group',
                    options: [
                        {
                            label: '关闭',
                            value: 0
                        },
                        {
                            label: '开启',
                            value: 1
                        }
                    ]
                }
            },
            {
                label: '账号',
                prop: 'account',
                component: {
                    name: 'el-input'
                }
            },
            {
                //【很重要】是否隐藏
                hidden({ scope }) {
                    // scope 为表单值
                    // 返回一个 boolean 来控制当前表单项的隐藏/显示
                    return scope.status != 1;
                },
                label: '密码',
                prop: 'password',
                component: {
                    name: 'el-input'
                }
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 布局 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>layout</el-tag>
            <span>布局</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/layout.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '布局',
        items: [
            {
                //【span】参考文档:https://element-plus.gitee.io/zh-CN/component/layout.html
                // 使用 1/24 分栏,默认 24
                span: 12,
                label: '昵称',
                prop: 'nickname',
                component: {
                    name: 'el-input'
                }
            },
            {
                span: 12,
                label: '手机号',
                prop: 'phone',
                component: {
                    name: 'el-input',
                    props: {
                        maxlength: 11
                    }
                }
            },
            {
                //【flex】使宽度不填充满
                flex: false,
                label: '标签',
                prop: 'label',
                component: {
                    name: 'el-input'
                }
            },
            {
                label: '状态',
                prop: 'status',
                value: 1,
                component: {
                    name: 'el-radio-group',
                    options: [
                        {
                            label: '开启',
                            value: 1
                        },
                        {
                            label: '关闭',
                            value: 0
                        }
                    ]
                }
            },
            {
                label: '备注',
                prop: 'remark',
                component: {
                    name: 'el-input',
                    props: {
                        type: 'textarea',
                        rows: 4
                    }
                }
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>open</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/open.vue']" />
            <!-- 自定义表单组件 -->
            <!--【很重要】ref 一定要对应 useForm 定义的值 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="tsx">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '起步',
        items: [
            {
                label: '昵称',
                // 绑定值的标识,表单提交及回显会自动根据 prop 获取对应的值
                prop: 'nickname',
                // 组件绑定
                component: {
                    // 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
                    name: 'el-input',
                    // 绑定的组件参数配置,如 clearable、placeholder 等
                    // 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
                    props: {
                        placeholder: '请输入昵称',
                        clearable: true,
                        onChange(value: string) {}
                    }
                }
            },
            {
                prop: 'age',
                component: {
                    name: 'el-input-number'
                },
                // 默认值,第一次打开有效
                value: 18
            }
        ],
        on: {
            // 打开时触发
            open() {
                console.log(Form.value?.validateField);
            },
            // 关闭时触发。当配置该方法时,关闭事件会被阻断,使用 done() 关闭窗口
            close(action, done) {
                // action 为关闭窗口的触发动作 "save" | "close"
                // done 关闭事件
                done();
            },
            // 提交时触发
            submit(data, { done, close }) {
                // data 为表单值
                // done 关闭加载事件、但不关闭窗口
                // close 关闭窗口
                close();
            }
        }
    });
}
</script>
```
## 选项框配置 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>options</el-tag>
            <span>选项框配置</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/options.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { computed, reactive } from 'vue';
const Form = useForm();
// 觉得麻烦就 any,如 { user: [] as any[] }
const options = reactive<{ [key: string]: { label: string; value: any }[] }>({
    user: []
});
function open() {
    Form.value?.open({
        title: '选项框配置',
        items: [
            {
                label: '下拉框',
                prop: 'select',
                component: {
                    name: 'el-select',
                    props: {
                        clearable: true // 可清除
                    },
                    options: [
                        {
                            label: 'javascript',
                            value: 1
                        },
                        {
                            label: 'vue',
                            value: 2
                        },
                        {
                            label: 'html',
                            value: 3
                        },
                        {
                            label: 'css',
                            value: 4
                        }
                    ]
                }
            },
            {
                label: '单选框',
                prop: 'radio',
                value: 1,
                component: {
                    name: 'el-radio-group',
                    options: [
                        {
                            label: '手机',
                            value: 1
                        },
                        {
                            label: '电脑',
                            value: 2
                        },
                        {
                            label: '电视',
                            value: 3
                        }
                    ]
                }
            },
            {
                label: '多选框',
                prop: 'checkbox',
                value: [2, 3],
                component: {
                    name: 'el-checkbox-group',
                    options: [
                        {
                            label: '咖啡',
                            value: 1
                        },
                        {
                            label: '汉堡',
                            value: 2
                        },
                        {
                            label: '炸鸡',
                            value: 3
                        },
                        {
                            label: '奶茶',
                            value: 4
                        }
                    ]
                }
            },
            {
                label: '动态配置1',
                prop: 'd1',
                component: {
                    name: 'el-select',
                    // 动态设置方法1,在 on.open 事件配置 options
                    options: []
                }
            },
            {
                label: '动态配置2',
                prop: 'd2',
                component: {
                    name: 'el-select',
                    // 动态设置方法2,使用 computed 更新 options
                    options: computed(() => options.user)
                }
            }
        ],
        on: {
            open() {
                // 模拟 1.5s 后取的数据
                setTimeout(() => {
                    // 动态设置方法1,使用 setOptions 方法设置
                    // d1 为 prop 值
                    Form.value?.setOptions('d1', [
                        {
                            label: '😊',
                            value: 1
                        },
                        {
                            label: '😭',
                            value: 2
                        },
                        {
                            label: '😘',
                            value: 3
                        }
                    ]);
                    // 动态设置方法2,直接设置 options.user,由 computed 更新
                    options.user = [
                        {
                            label: '💰',
                            value: 1
                        },
                        {
                            label: '🚗',
                            value: 2
                        }
                    ];
                }, 1500);
            },
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 插件的使用 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>plugin</el-tag>
            <span>插件的使用</span>
        </div>
        <div class="c">
            <el-button @click="open('manager')">管理者</el-button>
            <el-button @click="open('user')">用户</el-button>
            <demo-code :files="['form/plugin/index.vue', 'form/plugin/role.ts']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { setRole } from './role';
const Form = useForm();
function open(role: string) {
    Form.value?.open(
        {
            title: '插件的使用',
            items: [
                {
                    label: '姓名',
                    prop: 'name',
                    required: true,
                    component: {
                        name: 'el-input'
                    }
                },
                {
                    // 自定义参数 role,匹配插件传入的角色
                    role: 'user',
                    label: '面试职位',
                    prop: 'work',
                    value: 1,
                    component: {
                        name: 'el-radio-group',
                        options: [
                            {
                                label: '前端开发',
                                value: 1
                            },
                            {
                                label: '后端开发',
                                value: 2
                            },
                            {
                                label: 'UI设计',
                                value: 3
                            }
                        ]
                    }
                },
                {
                    role: 'user',
                    label: '期望薪资',
                    prop: 'salary',
                    value: 5000,
                    component: {
                        name: 'el-input-number',
                        props: {
                            min: 2000,
                            max: 100000
                        }
                    }
                },
                {
                    role: 'manager',
                    label: '入职时间',
                    prop: 'date',
                    component: {
                        name: 'el-date-picker'
                    }
                },
                {
                    role: 'manager',
                    label: '负责人',
                    prop: 'head',
                    component: {
                        name: 'el-input'
                    }
                }
            ],
            on: {
                submit(data, { done, close }) {
                    close();
                }
            }
        },
        [
            // 自定义插件,角色权限控制
            setRole(role)
        ]
    );
}
</script>
```
## 必填项配置 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>required</el-tag>
            <span>必填项配置</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/required.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form"></cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '必填项配置',
        items: [
            {
                label: '昵称',
                prop: 'nickname',
                component: {
                    name: 'el-input'
                },
                // 是否必填,默认判断绑定值是否空
                required: true
            },
            {
                label: '手机号',
                prop: 'phone',
                component: {
                    name: 'el-input',
                    props: {
                        maxlength: 11
                    }
                },
                // 自定义规则
                // 基础用法可参考:https://element-plus.gitee.io/zh-CN/component/form.html
                // 高级用法可参考:https://github.com/yiminghe/async-validator
                rules: [
                    {
                        required: true,
                        validator: (rule, value, callback) => {
                            if (value === '') {
                                callback(new Error('手机号不能为空'));
                            } else if (!/^1[3456789]\d{9}$/.test(value)) {
                                callback(new Error('手机号格式错误'));
                            } else {
                                callback();
                            }
                        }
                    }
                ]
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
</script>
```
## 添加/删除表单项 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>rules</el-tag>
            <span>添加/删除表单项</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['form/rules.vue']" />
            <!-- 自定义表单组件 -->
            <cl-form ref="Form">
                <template #slot-cert="{ scope }">
                    <div class="cert">
                        <!--【很重要】prop、rules 配置格式如下 -->
                        <el-form-item
                            v-for="(item, index) in scope.cert"
                            :key="index"
                            :label="`证书${index + 1}`"
                            :prop="`cert.${index}.label`"
                            :rules="{
                                message: `请填写证书${index + 1}`,
                                required: true
                            }"
                        >
                            <div class="row">
                                <!-- 输入框 -->
                                <el-input v-model="item.label" placeholder="请填写证书"></el-input>
                                <!-- 删除行 -->
                                <el-icon @click="rowDel(index)">
                                    <delete />
                                </el-icon>
                            </div>
                        </el-form-item>
                        <!-- 添加行 -->
                        <el-row type="flex" justify="end">
                            <el-button @click="rowAdd()">添加证书</el-button>
                        </el-row>
                    </div>
                </template>
            </cl-form>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { Delete } from '@element-plus/icons-vue';
const Form = useForm();
function open() {
    Form.value?.open({
        title: '添加/删除表单项',
        items: [
            {
                label: '昵称',
                prop: 'nickname',
                component: {
                    name: 'el-input'
                },
                required: true
            },
            {
                prop: 'cert',
                //【很重要】默认数据格式,以实际业务为主。
                value: [
                    {
                        label: ''
                    }
                ],
                component: {
                    name: 'slot-cert'
                }
            }
        ],
        on: {
            submit(data, { close }) {
                close();
            }
        }
    });
}
function rowAdd() {
    Form.value?.form.cert.push({
        label: ''
    });
}
function rowDel(index: number) {
    Form.value?.form.cert.splice(index, 1);
}
</script>
<style lang="scss" scoped>
.cert {
    .row {
        display: flex;
        align-items: center;
        .el-input {
            flex: 1;
            margin-right: 10px;
        }
        .el-icon {
            cursor: pointer;
            &:hover {
                color: red;
            }
        }
    }
}
</style>
```
.cursor/rules/module.mdc
New file
@@ -0,0 +1,464 @@
---
description: module | plugins 模块、插件
globs:
---
# 模块/插件开发
## 目录结构
在 `src/modules` 或 `src/plugins` 下添加一个目录 `demo`:
```js
demo
    ├──pages // 页面路由
    ├──views // 视图路由
    ├──hooks // 常用函数
    ├──components // 常用组件
    ├──directives // 指令
    ├──static // 静态文件目录
    ├──store // 状态管理
    ├──... // 其他自定义文件
    ├──config.ts // 配置文件
    └──index.ts // 入口文件
```
::: warning
约定的目录名称不可修改,但可自行添加或者删除。
:::
## pages、views
1. 页面参与权限控制,所以不主动注册目录下的路由,通过 `菜单列表` 中配置注册。或者在 `config.ts` 中手动配置:
```js
import { type ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    views: [
      {
        path: "/demo",
        meta: {
          label: "测试",
        },
        component: () => import("./views/demo.vue"),
      },
    ],
    pages: [
      {
        path: "/demo2",
        meta: {
          label: "测试",
        },
        component: () => import("./pages/demo.vue"),
      },
    ],
  };
};
```
2. 使页面参与路由缓存,配置 `name` 参数
:::warning
`path` 与 `name` 的匹配规则:
- /demo/t1 = demo-t1
- /demo/t1-det = demo-t1-det
:::
方式 1:
```html
<script lang="ts" setup>
  defineOptions({
    name: "demo",
  });
</script>
```
方式 2:
```html
<script lang="ts">
  export default defineComponent({
    name: "demo",
  });
</script>
```
## components
目录下的组件,全局注册配置方法如下:
```js
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    components: [
      import("./components/demo.vue"),
      import("./components/demo1.vue"),
    ],
  };
};
```
## directives
`directives` 会以目录下的文件名分别注册指令
```ts
// demo/directives/test.ts
export default {
    created(el, binding) {},
    mounted() {},
    ...
};
```
使用
```html
<div v-test></div>
```
## store
使用 `pinia` 的推荐写法:
```ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useTestStore = defineStore("test", function () {
  const count = ref(0);
  function add() {
    count.value += 1;
  }
  return {
    count,
    add,
  };
});
```
使用
```ts
import { useTestStore } from "/$/demo/store";
const test = useTestStore();
test.add();
console.log(test.count); // 1
```
::: tip
参考 `base` 模块下 `store` 的导出方式
:::
## config.ts
模块的配置,程序运行时会读取该文件。
- 全局组件、路由的导入
- 事件钩子
输入 `module-config` 关键字,`vscode` 中会自动生成:
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
  return {
    // 是否启用
    enable: true,
    // 插件名称
    label: "插件名称",
    // 插件描述
    description: "插件描述",
    // 作者
    author: "作者",
    version: "1.0.0",
    updateTime: "2024-02-02",
    logo: "",
    // 忽略
    ignore: {
      // 忽略进度条的请求
      NProgress: [
        "/base/open/eps",
        "/base/comm/person",
        "/base/comm/permmenu",
        "/base/comm/upload",
        "/base/comm/uploadMode",
      ],
      // 忽略 token 的路由
      token: ["/login", "/401", "/403", "/404", "/500", "/502"],
    },
    // 排序
    order: 0,
    // 配置参数
    options: {
      name: "神仙",
    },
    // 示例页面
    demo: [
      {
        name: "基础用法",
        component: () => import("..."),
      },
    ],
    // 注册全局组件
    components: [],
    // 视图路由
    views: [],
    // 页面路由
    pages: [],
    // 顶部工具栏
    toolbar: {
      order: 1,
      pc: true, // 是否在 pc 端显示
      h5: true, // 是否在 h5 端显示
      component: import("./components/index.vue"),
    },
    // 注入全局组件
    index: {
      component: import("./components/index.vue"),
    },
    // 安装时触发
    install(app: Vue) {},
    // 加载时触发
    onLoad(events) {},
  };
};
```
- order 模块加载顺序,值越大越先
- options 提供给外部使用的参数配置:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    options: {
      // 尺寸
      size: 120,
      // 显示文案
      text: "选择文件",
      // 限制
      limit: {
        // 上传最大数量
        upload: 9,
        // 文件空间选择数
        select: 9,
        // 上传大小限制
        size: 100,
      },
    },
  };
};
```
获取方式:
```ts
import { module } from "/@/cool";
const config = module.config("模块名");
```
- components 提供全局的组件:
```ts
import type { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    components: [import("./components/test.vue")],
  };
};
```
批量导入可以使用 [import.meta.glob](mdc:https:/vitejs.dev/guide/features.html#glob-import) 方法:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    components: Object.values(import.meta.glob("./components/**/*")),
  };
};
```
- views 全局注册的视图路由,存放在 `/` 中的子路由 `children`:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    views: [
      {
        path: "/test",
        meta: {
          label: "测试中心",
        },
        component: () => import("./views/test.vue"),
      },
    ],
  };
};
```
- pages 全局注册的页面路由:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
  return {
    pages: [
      {
        path: "/test",
        meta: {
          label: "测试中心",
        },
        component: () => import("./views/test.vue"),
      },
    ],
  };
};
```
- install 模块安装时触发。用于预先处理:
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
  return {
    install(app: Vue) {
      // 注册组件
      app.component("test", Test);
      // 注册指令
      app.directive("focus", {
        created(el, bind) {},
      });
    },
  };
};
```
- onLoad 模块安装时触发,预先加载数据,如菜单配置、用户信息:
  1.  使用 `await` 等待加载完成后往下执行
  2.  可往下模块导出某个方法和变量,如 `hasToken` 验证是否有登陆
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
  return {
    async onLoad() {
      const { user, menu } = useStore();
      if (user.token) {
        // 获取用户信息
        user.get();
        // 获取菜单权限
        await menu.get();
      }
      return {
        async hasToken(cb: () => Promise<any> | void) {
          if (user.token) {
            if (cb) await cb();
          }
        },
      };
    },
  };
};
```
其他模块中接收 `hasToken` 方法:
```ts
import { ModuleConfig } from "/@/cool";
import { useDict } from "./index";
export default (): ModuleConfig => {
  return {
    onLoad({ hasToken }) {
      const { dict } = useDict();
      hasToken(() => {
        dict.refresh();
      });
    },
  };
};
```
## index.ts
该模块需要对外开放的变量及方法,方便于别人直接使用:
```ts
// modules/test/index.ts
import { useStore } from "./store";
export function useTest() {
  return {
    // 导出 pinia
    ...useStore(),
    // 自定义方法
    test() {},
    // 自定义变量
    data: {
      description: "数据描述",
    },
  };
}
```
导出命名规则 `useBase` `useDemo` `useDict` use + 模块名
使用:
```ts
import { useTest } from "/$/test";
const { data, test } = useTest();
```
.cursor/rules/search.mdc
New file
@@ -0,0 +1,743 @@
---
description: cl-search 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>base</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['search/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="起步" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!--【很重要】搜索组件 -->
                        <cl-search ref="Search" />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true,
                    // 值改变的时候刷新列表
                    onChange(val: string) {
                        refresh({
                            name: val,
                            page: 1
                        });
                    }
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ],
    onChange(data, prop) {
        console.log(data, prop);
    }
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 折叠 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>collapse</el-tag>
            <span>折叠</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['search/collapse.vue']" />
            <!-- 折叠表格组件 -->
            <cl-dialog v-model="visible" title="折叠" width="80%">
                <cl-crud ref="Crud">
                    <!--【collapse】折叠参数,【inline】是否行内 -->
                    <cl-search ref="Search" reset-btn collapse :inline="false" />
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-12-26</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { range } from 'lodash-es';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-search 配置
const Search = useSearch({
    items: [
        ...range(20).map(i => {
            return {
                label: '输入框',
                prop: `T${i + 1}`,
                component: {
                    name: 'el-input'
                }
            };
        })
    ]
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
x
```
## 自定义 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>custom</el-tag>
            <span>自定义</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['search/custom.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="自定义" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!--【很重要】搜索组件 -->
                        <cl-search
                            ref="Search"
                            :reset-btn="true"
                            :on-load="onLoad"
                            :on-search="onSearch"
                        >
                            <!-- 自定义按钮 -->
                            <template #buttons="scope">
                                <el-button @click="toSearch(scope)">自定义按钮</el-button>
                            </template>
                        </cl-search>
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true,
                    // 值改变的时候刷新列表
                    onChange(val: string) {
                        refresh({
                            name: val,
                            page: 1
                        });
                    }
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
// cl-search 初始化
function onLoad(data: any) {
    data.name = '白小纯';
}
// cl-search 配置 onSearch 后,必须使用 next 方法继续请求
function onSearch(data: any, { next }: { next: (data: any) => void }) {
    ElMessage.info('开始搜索');
    // 这边可以处理其他事务
    next(data);
}
// 自定义搜索,data 为表单数据
function toSearch(data: any) {
    ElMessage.info('自定义搜索');
    refresh({
        page: 1,
        ...data
    });
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 布局 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>layout</el-tag>
            <span>布局</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['search/layout.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="布局" width="80%">
                <cl-crud ref="Crud">
                    <!--【很重要】搜索组件 -->
                    <cl-search ref="Search" :reset-btn="true" />
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
    // 取消行内表单模式
    inline: false,
    // 表单参数
    props: {
        labelPosition: 'top'
    },
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input',
                props: {
                    clearable: true,
                    // 值改变的时候刷新列表
                    onChange(val: string) {
                        refresh({
                            name: val,
                            page: 1
                        });
                    }
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input',
                props: {
                    clearable: true
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 使用插件 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>plugin</el-tag>
            <span>使用插件</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['search/layout.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="使用插件" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-flex1 />
                        <!--【很重要】搜索组件 -->
                        <cl-search ref="Search" />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { Plugins } from '/#/crud';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-search 配置
const Search = useSearch({
    // 【很重要】自动读取 service 下的 search 数据
    plugins: [
        Plugins.Search.setAuto({
            customComponent(field) {
                if (field.propertyName == 'name') {
                    return {
                        name: 'cl-select',
                        props: {
                            options: [
                                {
                                    label: '张三',
                                    value: '1'
                                },
                                {
                                    label: '李四',
                                    value: '2'
                                }
                            ]
                        }
                    };
                }
                // null 则不操作,按系统默认操作
                return null;
            }
        })
    ]
});
function refresh(params?: any) {
    Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
.cursor/rules/table.mdc
New file
@@ -0,0 +1,2117 @@
---
description: cl-table 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>base</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="起步" width="80%">
                <!--【很重要】需要包含在 cl-crud 组件内 -->
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 参数文档查看:https://element-plus.org/zh-CN/component/table.html#table-%E5%B1%9E%E6%80%A7 -->
                        <cl-table ref="Table" stripe></cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        // 测试数据,移步到 cl-crud 例子查看
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    // 是否自动计算表格高度,表格高度等于减去上区域和下区域的高度
    //【很重要】在弹窗或者上级不确定高度中,设置 autoHeight: false,避免显示异常。也可以手动设置最大高度 maxHeight: 500
    autoHeight: false,
    // 右键菜单,移步到右键菜单示例中查看
    contextMenu: ['refresh'],
    // 列配置,点击 columns 查看描述
    // 更多配置查看 el-table-column 文档,https://element-plus.org/zh-CN/component/table.html#table-column-%E5%B1%9E%E6%80%A7
    columns: [
        {
            // 是否为多选框操作列
            type: 'selection'
            // 是否为序号列
            // type: "index"
        },
        {
            // 表头标题
            label: '姓名',
            // 绑定值
            prop: 'name',
            // 最小宽度
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            // 字典匹配,移步到字典示例中查看
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            // 是否排序,desc, asc
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 多级表头 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>children</el-tag>
            <span>多级表头</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/children.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="多级表头" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '用户信息',
            prop: 'baseInfo',
            minWidth: 250,
            // 配置 children 参数
            children: [
                {
                    label: '姓名',
                    prop: 'name',
                    minWidth: 140
                },
                {
                    label: '手机号',
                    prop: 'phone',
                    minWidth: 140
                }
            ]
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 自定义列展示 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>column-custom</el-tag>
            <span>自定义列展示</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/column-custom.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="自定义列展示" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!--【很重要】组件配置,设置为 Table 的 columns,也可以自定义 -->
                        <cl-column-custom :columns="Table?.columns" />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table"></cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '状态',
            prop: 'status',
            dict: [
                {
                    label: '启用',
                    value: 1,
                    type: 'success'
                },
                {
                    label: '禁用',
                    value: 0,
                    type: 'danger'
                }
            ],
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 组件渲染 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>component</el-tag>
            <span>组件渲染</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/component/index.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="组件渲染" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table"></cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
import UserInfo from './user-info.vue';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140,
            //【很重要】组件实例方式渲染
            component: {
                vm: UserInfo
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140,
            //【很重要】组件名方式渲染
            component: {
                // 组件名,组件必须全局注册了
                name: 'el-input',
                // 传入参数
                props: {
                    onChange(val) {
                        ElMessage.info(val);
                    }
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## user-info 示例
```vue
<template>
    <div class="user-info">
        <cl-avatar :size="36" />
        <div class="det">
            <p>{{ scope?.name }}</p>
        </div>
    </div>
</template>
<!-- name 必须填写且唯一 -->
<script setup lang="ts">
defineOptions({
    name: 'user-info'
});
const props = defineProps({
    prop: String, // 列配置的 prop
    scope: null // 列数据
});
</script>
<style lang="scss" scoped>
.user-info {
    display: flex;
    align-items: center;
    .det {
        flex: 1;
        margin-left: 10px;
        text-align: left;
    }
}
</style>
```
## 右键菜单 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>context-menu</el-tag>
            <span>右键菜单</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/context-menu.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="右键菜单">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table"></cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
import { EditPen, MoreFilled } from '@element-plus/icons-vue';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    // 右键菜单配置,为 [] 时则不显示内容
    contextMenu: [
        'refresh', // 刷新
        'check', // 选择行
        'edit', // 弹出编辑框
        'delete', // 弹出删除提示
        'info', // 弹出详情
        'order-desc', // 使列倒序
        'order-asc', // 使列升序
        {
            label: '禁用状态',
            disabled: true
        },
        {
            label: '带图标',
            prefixIcon: EditPen,
            suffixIcon: MoreFilled
        },
        {
            label: '超出隐藏,看我有很多字非常多',
            ellipsis: true
        },
        {
            label: '多层级',
            children: [
                {
                    label: 'A',
                    children: [
                        {
                            label: 'A-1',
                            callback(done) {
                                ElMessage.success('点击了A-1');
                                done();
                            }
                        }
                    ]
                },
                {
                    label: 'B'
                },
                {
                    label: 'C'
                }
            ]
        },
        // row 行数据
        // column 列属性
        // event 事件对象
        (row, column, event) => {
            // 必须返回一个对象
            return {
                label: '自定义2',
                callback(done) {
                    ElMessage.info('获取中');
                    setTimeout(() => {
                        ElMessage.success('Ta 是' + row.name);
                        // 关闭右键菜单,只有在用到 callback 方法时才需要
                        done();
                    }, 500);
                }
            };
        }
    ],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
// cl-upsert 配置,详细移步到 cl-upsert 示例查看
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 字典匹配 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>dict</el-tag>
            <span>字典匹配</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/dict.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="字典匹配" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { computed, reactive, ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
const options = reactive({
    occupation: [] as { label: string; value: any }[]
});
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            //【很重要】字典匹配
            // 使用字典模块的 get 方法绑定,菜单地址 /dict/list
            dict: dict.get('occupation'),
            // 是否使用不同颜色区分
            dictColor: true,
            minWidth: 140
        },
        {
            label: '等级',
            prop: 'occupation',
            //【很重要】动态匹配列表的情况,使用 computed
            dict: computed(() => options.occupation),
            minWidth: 140
        },
        {
            label: '状态',
            prop: 'status',
            // 自定义匹配列表
            dict: [
                {
                    label: '启用',
                    value: 1,
                    type: 'success'
                },
                {
                    label: '禁用',
                    value: 0,
                    type: 'danger'
                }
            ],
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
    // 模拟接口获取数据
    setTimeout(() => {
        options.occupation = [
            {
                label: 'A',
                value: 0
            },
            {
                label: 'B',
                value: 1
            },
            {
                label: 'C',
                value: 2
            },
            {
                label: 'D',
                value: 3
            },
            {
                label: 'E',
                value: 4
            },
            {
                label: 'F',
                value: 5
            }
        ];
    }, 1500);
}
</script>
```
## 数据格式化 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>formatter</el-tag>
            <span>数据格式化</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/formatter.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="数据格式化" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-09-26</span>
        </div>
    </div>
</template>
<script setup lang="tsx">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140,
            formatter(row) {
                return '📱' + row.phone;
            }
        },
        {
            label: '用户信息',
            minWidth: 200,
            // tsx 方式渲染
            // 【很重要】使用 tsx 语法时,script 的 lang 一定要设置为 tsx
            formatter(row) {
                // row 为当前行数据
                return (
                    <el-row>
                        <cl-avatar size={30} />
                        <el-text style={{ marginLeft: '10px' }}>{row.name}</el-text>
                    </el-row>
                );
            }
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 隐藏/显示 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>hidden</el-tag>
            <span>隐藏/显示</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/hidden.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="隐藏/显示" width="80%">
                <cl-crud ref="Crud">
                    <!--配置一个 tab -->
                    <el-tabs v-model="active">
                        <el-tab-pane label="员工" name="user"></el-tab-pane>
                        <el-tab-pane label="企业" name="company"></el-tab-pane>
                    </el-tabs>
                    <cl-row>
                        <!-- 使用方法 showColumn 显示 -->
                        <el-button @click="showColumn('account')">显示账号</el-button>
                        <!-- 使用方法 hideColumn 隐藏 -->
                        <el-button @click="hideColumn('account')">隐藏账号</el-button>
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table"></cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { computed, ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        // 测试数据,移步到 cl-crud 例子查看
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
const active = ref('user');
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: 'ID',
            prop: 'id',
            minWidth: 140,
            //【很重要】配置 hidden 参数,格式为 boolean 或者 Vue.ComputedRef<boolean>
            hidden: computed(() => {
                return active.value != 'company';
            })
        },
        {
            label: '账号',
            prop: 'account',
            minWidth: 140,
            hidden: true // 默认 false
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
function showColumn(prop: string) {
    Table.value?.showColumn(prop);
}
function hideColumn(prop: string) {
    Table.value?.hideColumn(prop);
}
</script>
```
## 操作栏 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>op</el-tag>
            <span>操作栏</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/op.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="操作栏" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table">
                            <!-- 插槽的渲染方式 #[component.name] -->
                            <template #slot-btns="{ scope }">
                                <el-button
                                    @click="
                                        () => {
                                            ElMessage.info(scope.row.name);
                                        }
                                    "
                                    >插槽按钮</el-button
                                >
                            </template>
                        </cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!-- 新增、编辑 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            //【很重要】type 必须是 op
            type: 'op',
            width: 410, // 宽度
            //【很重要】操作按钮配置,edit 和 info 必须搭配 cl-upsert 实现
            // edit 编辑,预先获取 service 的 info 接口数据,并带入 cl-upsert 的表单值中
            // info 详情,cl-upsert 内的组件全部传入 disabled 参数
            // delete 删除,调用 service 的 delete 接口删除行数据
            buttons: [
                {
                    label: '编辑',
                    type: 'primary',
                    onClick({ scope }) {
                        ElMessage.info(scope.row.name);
                    }
                },
                {
                    label: '删除',
                    type: 'danger',
                    onClick({ scope }) {
                        ElMessage.info(scope.row.name);
                    }
                },
                {
                    label: '更多',
                    type: 'success',
                    children: [
                        {
                            label: '查看',
                            onClick({ scope }) {
                                ElMessage.info(scope.row.name);
                            }
                        },
                        {
                            label: '禁用',
                            onClick({ scope }) {
                                ElMessage.info(scope.row.name);
                            }
                        }
                    ]
                },
                {
                    name: 'slot-btns'
                }
            ]
        }
    ]
});
// cl-upsert 配置,详细移步到 cl-upsert 示例查看
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true, // 树形方式选择
                    checkStrictly: true, // 任意层级都能点
                    options: dict.get('occupation') // 使用字典数据
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 插件的使用 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>plugin</el-tag>
            <span>插件的使用</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/plugin/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="插件的使用" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="tsx">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { merge } from 'lodash-es';
import { defineComponent } from 'vue';
// 插件:列标签匹配,方便多个列表公用同一个组件
function setColumn(): ClTable.Plugin {
    const columns = {
        UserInfo: {
            label: '用户信息',
            minWidth: 200,
            component: {
                vm: defineComponent({
                    name: 'user-info',
                    props: {
                        scope: null
                    },
                    setup(props) {
                        return () => {
                            return (
                                <div>
                                    <p>{props.scope.name}</p>
                                    <p>{props.scope.phone}</p>
                                </div>
                            );
                        };
                    }
                })
            }
        }
    } as { [key: string]: DeepPartial<ClTable.Column> };
    return ({ exposed }) => {
        function deep(arr: ClTable.Column[]) {
            arr.forEach(e => {
                if (e.tag) {
                    merge(e, columns[e.tag]);
                }
                deep(e.children || []);
            });
        }
        deep(exposed.columns);
    };
}
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            type: 'selection'
        },
        {
            tag: 'UserInfo'
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ],
    //【很重要】配置插件
    plugins: [setColumn()]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 行编辑 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>row-edit</el-tag>
            <span>行编辑</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/plugin/row-edit.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="行编辑" width="80%">
                <cl-crud ref="Crud">
                    <el-text class="mb-4" tag="p">点击姓名、手机号可以进行编辑</el-text>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { Plugins } from '/#/crud';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140,
            // 【很重要】行编辑,默认 el-input
            edit: true
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140,
            // 【很重要】行编辑,开启、关闭
            edit: {
                enable: true
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140,
            edit: {
                enable: true,
                // 【很重要】行编辑,组件配置
                component: {
                    name: 'cl-select',
                    props: {
                        options: dict.get('occupation'),
                        tree: true
                    }
                }
            }
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc',
            // 【很重要】行编辑,组件配置
            edit: {
                enable: true,
                component: {
                    name: 'el-date-picker',
                    props: {
                        type: 'date',
                        valueFormat: 'YYYY-MM-DD'
                    }
                }
            }
        },
        {
            type: 'op',
            buttons: ['delete']
        }
    ],
    //【很重要】行编辑插件
    plugins: [Plugins.Table.rowEdit()]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 表头搜索 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>search</el-tag>
            <span>表头搜索</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/search.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="表头搜索" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="tsx">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { Plus } from '@element-plus/icons-vue';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140,
            //【很重要】搜索参数配置
            search: {
                isInput: false, // 默认false,是否输入框模式
                value: '', // 默认值
                refreshOnChange: true, // 默认false,搜索时刷新数据,service 的 page 接口请求参数为 { page: 1, [绑定的prop]: 输入值 }
                // 自定义渲染组件
                component: {
                    name: 'el-input',
                    props: {
                        placeholder: '搜索姓名'
                    }
                }
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140,
            //【很重要】搜索参数配置
            search: {
                // 是否显示搜索图标
                icon: () => <Plus />,
                // 自定义渲染组件
                component: {
                    name: 'el-input',
                    props: {
                        placeholder: '搜索手机号',
                        // 自定义 change 事件
                        onChange(val) {
                            Crud.value?.refresh({
                                page: 1,
                                phone: val
                            });
                        }
                    }
                }
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140,
            //【很重要】搜索参数配置
            search: {
                // 是否显示搜索图标
                icon: () => <cl-svg name="icon-app" size={13} />,
                // 自定义渲染组件
                component: {
                    name: 'cl-select',
                    props: {
                        placeholder: '搜索工作',
                        options: dict.get('occupation')
                    }
                }
            }
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 多选框数据 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>selection</el-tag>
            <span>多选框数据</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/selection.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="多选框数据" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <el-button @click="selectRow">选中2行</el-button>
                        <el-button :disabled="Table?.selection.length == 0" @click="clear">
                            取消选择
                        </el-button>
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <el-text>已选 {{ Table?.selection.length }} 人</el-text>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh', 'check'],
    columns: [
        {
            type: 'selection'
        },
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
function selectRow() {
    const [a, b] = Table.value?.data || [];
    // 选中2个
    Table.value?.toggleRowSelection(a);
    Table.value?.toggleRowSelection(b);
}
function clear() {
    Table.value?.clearSelection();
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 插槽的使用 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>slot</el-tag>
            <span>插槽的使用</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/slot.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="插槽的使用" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table">
                            <!--【很重要】必须与 prop 名保持一致,格式:column-[prop] -->
                            <template #column-name="{ scope }">
                                <cl-row type="flex" align="middle">
                                    <cl-avatar :size="36" :style="{ marginRight: '10px' }" />
                                    <el-text>{{ scope.row.name }}</el-text>
                                </cl-row>
                            </template>
                            <template #column-phone="{ scope }"> 📱{{ scope.row.phone }} </template>
                        </cl-table>
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            headerAlign: 'left',
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 合并行或列 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>span-method</el-tag>
            <span>合并行或列</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/span-method.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="合并行或列" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" :span-method="onSpanMethod" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '存款',
            prop: 'wages',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
interface SpanMethodProps {
    row: any;
    column: any;
    rowIndex: number;
    columnIndex: number;
}
function onSpanMethod({ row, column, rowIndex, columnIndex }: SpanMethodProps) {
    // 根据实际业务需求调整返回值 { rowspan, colspan }
    if (columnIndex === 0) {
        if (rowIndex % 2 === 0) {
            return {
                rowspan: 2,
                colspan: 1
            };
        } else {
            return {
                rowspan: 0,
                colspan: 0
            };
        }
    }
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 表尾合计行 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>summary</el-tag>
            <span>表尾合计行</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['table/summary.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="表尾合计行" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <cl-table ref="Table" show-summary :summary-method="getSummaries" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '存款',
            prop: 'wages',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        }
    ]
});
function getSummaries() {
    return ['合计', '$' + Table.value?.data.reduce((a, b) => a + b.wages, 0)];
}
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
.cursor/rules/upsert.mdc
New file
@@ -0,0 +1,716 @@
---
description: cl-upsert 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>base</el-tag>
            <span>起步</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['upsert/base.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="起步" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 打开新增表单的按钮 -->
                        <cl-add-btn />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】新增、编辑的表单组件 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            // edit 打开编辑表单
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Upsert = useUpsert({
    // 配置如 cl-form 一样
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 打开、关闭、提交等事件 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>event</el-tag>
            <span>打开、关闭、提交等事件</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['upsert/event.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="事件" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 打开新增表单的按钮 -->
                        <cl-add-btn />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】新增、编辑的表单组件 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';
const { service } = useCool();
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            // edit 打开编辑表单
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ],
    // 以下事件按顺序触发
    // 弹窗打开的事件,这个时候还未有表单数据
    onOpen() {
        console.log('onOpen');
    },
    // 获取详情,编辑的时候会触发
    async onInfo(data, { next, done }) {
        // 不配置 onInfo 的时候默认执行 next(data),调用 service 的 info 接口获取详情
        // next(data);
        // 自定义,需要对请求数据进行处理或者返回处理后的数据
        const res = await next({
            id: data.id
        });
        done({
            ...res,
            name: `[${res.name}]`
        });
    },
    // 弹窗打开后,已经得到了表单数据
    onOpened(data) {
        // 判定是否编辑模式
        if (Upsert.value?.mode == 'update') {
            // 对数据处理
            data.phone += '000';
        }
    },
    // 提交事件的钩子
    // data 表单提交数据
    // next 继续往下执行
    // done 关闭加载
    // close 关闭弹窗
    async onSubmit(data, { next, done, close }) {
        // 不配置 onSubmit 的时候默认执行 next(data),提交后会去请求 service 的 update/add 接口
        // next(data);
        // 自定义如下
        // 场景1:提交时对参数额外的处理
        // next({
        //     ...data,
        //     status: 1,
        //     createTime: dayjs().format("YYYY-MM-DD")
        // });
        // 场景2:提交前、后的操作
        // 之前,模拟获取 userId
        const userId = await service.base.sys.user.info({ id: 1 });
        // 返回值
        const res = await next({
            userId,
            data
        });
        // 之后
        // console.log(res);
    },
    // 关闭时触发
    onClose(action, done) {
        // action 关闭的类型
        console.log('action,', action);
        // 使用 done 关闭窗口
        done();
    },
    // 关闭后触发
    onClosed() {
        console.log('onClosed');
    }
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## Hook的使用 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>hook</el-tag>
            <span>Hook的使用</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['upsert/hook/index.vue', 'upsert/hook/reg-pca2.ts']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="Hook的使用" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 打开新增表单的按钮 -->
                        <cl-add-btn />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】新增、编辑的表单组件 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '省市区',
            prop: 'pca',
            formatter(row) {
                return row.province ? row.province + '-' + row.city + '-' + row.district : '-';
            },
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            buttons: ['edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '手机号',
            prop: 'phone',
            component: {
                name: 'el-input'
            }
        },
        {
            label: '省市区',
            prop: 'pca2',
            //【很重要】hook 参数配置
            hook: {
                bind(value, { form }) {
                    // 将3个参数合并成一个数组,带入级联选择器
                    return [form.province, form.city, form.district];
                },
                submit(value, { form, prop }) {
                    // 提交的时候将数组拆分成3个字段提交
                    const [province, city, district] = value || [];
                    form.province = province;
                    form.city = city;
                    form.district = district;
                    // 删除 prop 绑定值
                    form[prop] = undefined;
                }
            },
            // 注册到全局后可直接使用,注册代码看 ./reg-pca2.ts
            // hook: "pca2",
            component: {
                name: 'cl-distpicker'
            }
        },
        {
            label: '标签',
            prop: 'labels',
            //【很重要】使用内置方法,避免一些辣鸡后端要你这么传给他
            hook: {
                // labels 的数据为 1,2,3
                // 绑定的时候将 labels 按 , 分割成数组
                bind: ['split', 'number'],
                // 提交的时候将 labels 拼接成字符串
                submit: ['join']
            },
            component: {
                name: 'el-select',
                props: {
                    multiple: true
                },
                options: [
                    {
                        label: '帅气',
                        value: 1
                    },
                    {
                        label: '多金',
                        value: 2
                    },
                    {
                        label: '有才华',
                        value: 3
                    }
                ]
            }
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ]
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
## 新增、编辑、详情模式 示例
```vue
<template>
    <div class="scope">
        <div class="h">
            <el-tag size="small" effect="dark" disable-transitions>mode</el-tag>
            <span>新增、编辑、详情模式</span>
        </div>
        <div class="c">
            <el-button @click="open">预览</el-button>
            <demo-code :files="['upsert/mode.vue']" />
            <!-- 自定义表格组件 -->
            <cl-dialog v-model="visible" title="不同模式" width="80%">
                <cl-crud ref="Crud">
                    <cl-row>
                        <!-- 打开新增表单的按钮 -->
                        <cl-add-btn />
                    </cl-row>
                    <cl-row>
                        <cl-table ref="Table" />
                    </cl-row>
                    <cl-row>
                        <cl-flex1 />
                        <cl-pagination />
                    </cl-row>
                    <!--【很重要】新增、编辑的表单组件 -->
                    <cl-upsert ref="Upsert" />
                </cl-crud>
            </cl-dialog>
        </div>
        <div class="f">
            <span class="date">2024-01-01</span>
        </div>
    </div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
    {
        service: 'test'
    },
    app => {
        app.refresh();
    }
);
// cl-table 配置
const Table = useTable({
    autoHeight: false,
    contextMenu: ['refresh'],
    columns: [
        {
            label: '姓名',
            prop: 'name',
            minWidth: 140
        },
        {
            label: '手机号',
            prop: 'phone',
            minWidth: 140
        },
        {
            label: '工作',
            prop: 'occupation',
            dict: dict.get('occupation'),
            minWidth: 140
        },
        {
            label: '创建时间',
            prop: 'createTime',
            minWidth: 170,
            sortable: 'desc'
        },
        {
            type: 'op',
            width: 240,
            buttons: ['info', 'edit', 'delete']
        }
    ]
});
// cl-upsert 配置
const Upsert = useUpsert({
    items: [
        {
            label: '姓名',
            prop: 'name',
            component: {
                name: 'el-input'
            }
        },
        //【很重要】只有返回方法的时候才能使用 Upsert
        () => {
            return {
                label: '手机号',
                prop: 'phone',
                // 新增的时候隐藏
                // hidden: Upsert.value?.mode == "add",
                component: {
                    name: 'el-input',
                    props: {
                        // 编辑的时候禁用
                        disabled: Upsert.value?.mode == 'update'
                    }
                }
            };
        },
        {
            label: '工作',
            prop: 'occupation',
            component: {
                name: 'cl-select',
                props: {
                    tree: true,
                    checkStrictly: true,
                    options: dict.get('occupation')
                }
            }
        }
    ],
    onOpen() {
        ElMessage.info(`当前模式:` + Upsert.value?.mode);
    }
});
const visible = ref(false);
function open() {
    visible.value = true;
}
</script>
```
.cursorrules
New file
@@ -0,0 +1,74 @@
# 项目背景
- 库:typescript、javaScript、scss、vue、tailwind
- 框架:cool-admin-vue
- 项目版本:8.x
# 项目目录
├── .vscode(代码片段,根据关键字可以快速地生成代码)
├── public(静态资源文件)
├── packages(源码包:@cool-vue/crud、@cool-vue/vite-plugin)
├── build
│ └── cool()
│ │ └── eps.json(Eps 配置文件)
│ │ └── eps.d.ts(Eps 描述文件)
├── src
│ └── cool(核心文件)
│ └── modules(项目模块)
│ │ └── base(基础模块)
│ │ └── demo(示例模块)
│ │ └── dict(字典模块)
│ │ └── helper(辅助模块)
│ │ └── recycle(回收站模块)
│ │ └── space(cl-upload-space 文件空间模块)
│ │ └── task(任务模块)
│ │ └── user(用户模块)
│ └── plugins(项目插件)
│ │ └── crud(cl-crud、@cool-vue/crud)
│ │ └── distpicker(cl-distpicker、省市区选择器)
│ │ └── echarts(图标)
│ │ └── editor-preview(编辑器预览组件)
│ │ └── editor-wange(wang富文本编辑器)
│ │ └── element-ui(element-plus 组件)
│ │ └── excel(excel导入、导出组件)
│ │ └── i18n(多语言)
│ │ └── iconfont(iconfont 图标)
│ │ └── theme(cl-theme 主题组件)
│ │ └── upload(cl-upload 文件上传组件)
│ │ └── view(cl-view-group、cl-view-head 视图组件)
│ └── config
│ │ └── index.ts(默认配置)
│ │ └── dev.ts(开发环境)
│ │ └── prod.ts(生产环境)
│ │ └── proxy.ts(代理配置)
│ └── App.vue(入口文件)
│ └── main.ts(入口文件)
├── package.json(依赖管理,项目信息)
└── ...
模块、插件目录
├── modules/plugins
│ └── base(模块名)
│ │ └── components(全局组件)
│ │ └── directives(全局指令)
│ │ └── locales(国际化)
│ │ └── router(路由)
│ │ └── store(状态管理)
│ │ └── utils(工具函数)
│ │ └── views(视图)
│ │ └── config.ts(必须,模块的配置)
│ │ └── index.ts(模块导出)
# 其它
- 文件、组件命名用 - 连接,如:student-info.vue
- service 的描述类型,查看 build/cool/eps.d.ts 描述文件
- 创建模块、插件代码需要读取.cursor/rules的module.mdc,其它的rules根据需要进行参考
# import 引用别名
- "/@" 对应 "./src"
- "/$" 对应 "./src/modules"
- "/#" 对应 "./src/plugins"
- "/~" 对应 "./packages"
.dockerignore
New file
@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.editorconfig
New file
@@ -0,0 +1,7 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
charset = utf-8
end_of_line = lf
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
.env
New file
@@ -0,0 +1,5 @@
# 应用名称
VITE_NAME = "COOL-ADMIN"
# 网络超时请求时间
VITE_TIMEOUT = 30000
.gitattributes
New file
@@ -0,0 +1,4 @@
*.js text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.vue text eol=lf
.gitignore
New file
@@ -0,0 +1,20 @@
.DS_Store
node_modules/
/dist/
dist-ssr/
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.project
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
vite.config.ts.timestamp*
.hintrc
New file
@@ -0,0 +1,16 @@
{
  "extends": [
    "development"
  ],
  "hints": {
    "meta-viewport": "off",
    "axe/text-alternatives": [
      "default",
      {
        "document-title": "off"
      }
    ],
    "disown-opener": "off",
    "css-prefix-order": "off"
  }
}
.prettierrc.json
New file
@@ -0,0 +1,10 @@
{
    "$schema": "https://json.schemastore.org/prettierrc",
    "semi": true,
    "useTabs": true,
    "tabWidth": 4,
    "printWidth": 100,
    "singleQuote": true,
    "arrowParens": "avoid",
    "trailingComma": "none"
}
.vscode/config.code-snippets
New file
@@ -0,0 +1,15 @@
{
    "module-config": {
        "prefix": "module-config",
        "scope": "typescript",
        "body": [
            "import { type ModuleConfig } from \"/@/cool\";",
            "",
            "export default (): ModuleConfig => {",
            "    return {};",
            "};",
            ""
        ],
        "description": "module config snippets"
    }
}
.vscode/crud.code-snippets
New file
@@ -0,0 +1,100 @@
{
    "cl-crud": {
        "prefix": "cl-crud",
        "scope": "vue",
        "body": [
            "<template>",
            "    <cl-crud ref=\"Crud\">",
            "        <cl-row>",
            "            <!-- 刷新按钮 -->",
            "            <cl-refresh-btn />",
            "            <!-- 新增按钮 -->",
            "            <cl-add-btn />",
            "            <!-- 删除按钮 -->",
            "            <cl-multi-delete-btn />",
            "            <cl-flex1 />",
            "            <!-- 条件搜索 -->",
            "            <cl-search ref=\"Search\" />",
            "        </cl-row>",
            "",
            "        <cl-row>",
            "            <!-- 数据表格 -->",
            "            <cl-table ref=\"Table\" />",
            "        </cl-row>",
            "",
            "        <cl-row>",
            "            <cl-flex1 />",
            "            <!-- 分页控件 -->",
            "            <cl-pagination />",
            "        </cl-row>",
            "",
            "        <!-- 新增、编辑 -->",
            "        <cl-upsert ref=\"Upsert\" />",
            "    </cl-crud>",
            "</template>",
            "",
            "<script lang=\"ts\" setup>",
            "defineOptions({",
            "    name: \"$1\"",
            "});",
            "",
            "import { useCrud, useTable, useUpsert, useSearch } from \"@cool-vue/crud\";",
            "import { useCool } from \"/@/cool\";",
            "",
            "const { service } = useCool();",
            "",
            "// cl-upsert",
            "const Upsert = useUpsert({",
            "    items: []",
            "});",
            "",
            "// cl-table",
            "const Table = useTable({",
            "    columns: []",
            "});",
            "",
            "// cl-search",
            "const Search = useSearch();",
            "",
            "// cl-crud",
            "const Crud = useCrud(",
            "    {",
            "        service: service$2",
            "    },",
            "    (app) => {",
            "        app.refresh();",
            "    }",
            ");",
            "",
            "// 刷新",
            "function refresh(params?: any) {",
            "    Crud.value?.refresh(params);",
            "}",
            "</script>",
            ""
        ],
        "description": "cl-crud snippets"
    },
    "cl-filter": {
        "prefix": "cl-filter",
        "scope": "html",
        "body": [
            "<cl-filter label=\"\">",
            "    <cl-select :options=\"[$1]\" prop=\"\" />",
            "</cl-filter>"
        ],
        "description": "cl-filter snippets"
    },
    "slot-item": {
        "prefix": "slot item",
        "scope": "html",
        "body": ["<template #slot-$1=\"{ scope }\">", "</template>", ""],
        "description": "slot snippets"
    },
    "slot-column": {
        "prefix": "slot column",
        "scope": "html",
        "body": ["<template #column-$1=\"{ scope }\">", "    ", "</template>", ""],
        "description": "column slot snippets"
    }
}
.vscode/extensions.json
New file
@@ -0,0 +1,3 @@
{
    "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
.vscode/settings.json
New file
@@ -0,0 +1,17 @@
{
    "editor.cursorSmoothCaretAnimation": "on",
    "explorer.fileNesting.enabled": true,
    "explorer.fileNesting.patterns": {
        "tsconfig.json": "tsconfig.*.json, env.d.ts",
        "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
        "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .prettier*, prettier*, .editorconfig"
    },
    "editor.codeActionsOnSave": {
        "source.fixAll": "explicit"
    },
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "[scss]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    }
}
Dockerfile
New file
@@ -0,0 +1,14 @@
FROM node:lts-alpine
WORKDIR /build
# 设置npm镜像
RUN npm config set registry https://registry.npmmirror.com
COPY package.json /build/package.json
RUN npm install
COPY ./ /build
RUN npm run build
FROM nginx
RUN mkdir /app
COPY --from=0 /build/dist /app
COPY --from=0 /build/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
LICENSE
New file
@@ -0,0 +1,33 @@
MIT License
Copyright (c) [2025] [厦门闪酷科技开发有限公司]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
MIT 许可证
版权所有 (c) [2025] [厦门闪酷科技开发有限公司]
特此免费授予获得本软件及相关文档文件(“软件”)副本的任何人无限制地处理本软件的权限,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件的副本,并允许软件提供给其的人员这样做,但须符合以下条件:
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
本软件按“原样”提供,不提供任何明示或暗示的担保,包括但不限于对适销性、特定用途适用性和非侵权的担保。在任何情况下,作者或版权持有人均不对因软件或软件使用或其他交易而产生的任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权诉讼或其他诉讼中。
build/cool/eps.d.ts
New file
@@ -0,0 +1,3756 @@
declare namespace Eps {
    interface BaseSysDepartmentEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 部门名称
         */
        name?: string;
        /**
         * 创建者ID
         */
        userId?: number;
        /**
         * 上级部门ID
         */
        parentId?: number;
        /**
         * 排序
         */
        orderNum?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BaseSysLogEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 用户ID
         */
        userId?: number;
        /**
         * 行为
         */
        action?: string;
        /**
         * ip
         */
        ip?: string;
        /**
         * 参数
         */
        params?: any;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 姓名
         */
        name?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BaseSysMenuEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 父菜单ID
         */
        parentId?: number;
        /**
         * 菜单名称
         */
        name?: string;
        /**
         * 菜单地址
         */
        router?: string;
        /**
         * 权限标识
         */
        perms?: string;
        /**
         * 类型 0-目录 1-菜单 2-按钮
         */
        type?: number;
        /**
         * 图标
         */
        icon?: string;
        /**
         * 排序
         */
        orderNum?: number;
        /**
         * 视图地址
         */
        viewPath?: string;
        /**
         * 路由缓存
         */
        keepAlive?: boolean;
        /**
         * 是否显示
         */
        isShow?: boolean;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BaseSysParamEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 键
         */
        keyName?: string;
        /**
         * 名称
         */
        name?: string;
        /**
         * 数据
         */
        data?: string;
        /**
         * 数据类型 0-字符串 1-富文本 2-文件
         */
        dataType?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BaseSysRoleEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 用户ID
         */
        userId?: string;
        /**
         * 名称
         */
        name?: string;
        /**
         * 角色标签
         */
        label?: string;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 数据权限是否关联上下级
         */
        relevance?: boolean;
        /**
         * 菜单权限
         */
        menuIdList?: any;
        /**
         * 部门权限
         */
        departmentIdList?: any;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BaseSysUserEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 部门ID
         */
        departmentId?: number;
        /**
         * 创建者ID
         */
        userId?: number;
        /**
         * 姓名
         */
        name?: string;
        /**
         * 用户名
         */
        username?: string;
        /**
         * 密码
         */
        password?: string;
        /**
         * 密码版本, 作用是改完密码,让原来的token失效
         */
        passwordV?: number;
        /**
         * 昵称
         */
        nickName?: string;
        /**
         * 头像
         */
        headImg?: string;
        /**
         * 手机
         */
        phone?: string;
        /**
         * 邮箱
         */
        email?: string;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 状态 0-禁用 1-启用
         */
        status?: number;
        /**
         * socketId
         */
        socketId?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataDriverEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 身份证号
         */
        businessId?: string;
        /**
         * 司机姓名
         */
        businessName?: string;
        /**
         * 手机号
         */
        phone?: string;
        /**
         * 驾驶证号
         */
        licenseNo?: string;
        /**
         * 驾驶证有效期开始
         */
        licenseStartDate?: Date;
        /**
         * 驾驶证有效期结束
         */
        licenseEndDate?: Date;
        /**
         * 所属单位
         */
        departmentId?: BigInt;
        /**
         * 状态
         */
        status?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataIotEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 终端编码
         */
        iotCode?: string;
        /**
         * 终端名称
         */
        iotName?: string;
        /**
         * 终端类型
         */
        iotTypeCode?: string;
        /**
         * 关联业务表
         */
        businessType?: string;
        /**
         * 业务表ID
         */
        businessId?: string;
        /**
         * 归属单位
         */
        departmentId?: number;
        /**
         * 终端Key
         */
        iotKey?: string;
        /**
         * 终端Secret
         */
        iotSecret?: string;
        /**
         * 终端上报渠道
         */
        iotChanel?: string;
        /**
         * 终端通信卡
         */
        iotPhone?: string;
        /**
         * 通信卡流量包
         */
        iotPhonePackage?: string;
        /**
         * 安装位置描述
         */
        installLocation?: string;
        /**
         * 数据格式
         */
        dataFormat?: string;
        /**
         * 通信协议
         */
        protocol?: string;
        /**
         * 采集方向
         */
        dataDirection?: string;
        /**
         * 采集频率
         */
        frequency?: string;
        /**
         * 数据存储位置
         */
        storageLocation?: string;
        /**
         * 状态
         */
        status?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataSiteEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 编码
         */
        businessId?: string;
        /**
         * 名称
         */
        businessName?: string;
        /**
         * 归属单位
         */
        departmentId?: number;
        /**
         * 站点类型
         */
        siteType?: string;
        /**
         * 地址
         */
        address?: string;
        /**
         * 负责人
         */
        leader?: string;
        /**
         * 经度
         */
        longitude?: number;
        /**
         * 纬度
         */
        latitude?: number;
        /**
         * 照片
         */
        photo?: string;
        /**
         * 是否启用
         */
        status?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataSorterEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 身份证号
         */
        businessId?: string;
        /**
         * 姓名
         */
        businessName?: string;
        /**
         * 手机号
         */
        phone?: string;
        /**
         * 入职日期
         */
        hireDate?: Date;
        /**
         * 状态
         */
        status?: number;
        /**
         * 所属单位
         */
        departmentId?: BigInt;
        /**
         * 工作站点编码
         */
        workSiteId?: string;
        /**
         * 身份证正面照
         */
        idCardFront?: string;
        /**
         * 身份证反面照
         */
        idCardBack?: string;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 名称
         */
        workSiteName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataVehicleEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 车牌号
         */
        businessId?: string;
        /**
         * 车型
         */
        vehicleType?: string;
        /**
         * 载重吨数
         */
        capacityTon?: number;
        /**
         * 默认司机
         */
        defaultDriver?: BigInt;
        /**
         * 所属单位
         */
        departmentId?: BigInt;
        /**
         * 状态
         */
        status?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface BasicdataWastetypeEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 代码
         */
        code?: string;
        /**
         * 名称
         */
        name?: string;
        /**
         * 是否可回收
         */
        isRecyclable?: number;
        /**
         * 单价
         */
        price?: number;
        /**
         * 货币
         */
        currency?: string;
        /**
         * 桶/袋重
         */
        packageWeight?: number;
        /**
         * 碳减排方向
         */
        carbonDirection?: number;
        /**
         * 碳减因数
         */
        carbonFactor?: number;
        /**
         * 因数单位
         */
        factorUnit?: string;
        /**
         * 公式表达式
         */
        formula?: string;
        /**
         * 来源标准
         */
        sourceStandard?: string;
        /**
         * 是否启用
         */
        status?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface DemoGoodsEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 标题
         */
        title?: string;
        /**
         * 价格
         */
        price?: number;
        /**
         * 描述
         */
        description?: string;
        /**
         * 主图
         */
        mainImage?: string;
        /**
         * 分类
         */
        type?: number;
        /**
         * 状态
         */
        status?: number;
        /**
         * 示例图
         */
        exampleImages?: any;
        /**
         * 库存
         */
        stock?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 昵称
         */
        userName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface DictInfoEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 类型ID
         */
        typeId?: number;
        /**
         * 名称
         */
        name?: string;
        /**
         * 值
         */
        value?: string;
        /**
         * 排序
         */
        orderNum?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 父ID
         */
        parentId?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface DictTypeEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 名称
         */
        name?: string;
        /**
         * 标识
         */
        key?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PluginInfoEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 名称
         */
        name?: string;
        /**
         * 简介
         */
        description?: string;
        /**
         * Key名
         */
        keyName?: string;
        /**
         * Hook
         */
        hook?: string;
        /**
         * 描述
         */
        readme?: string;
        /**
         * 版本
         */
        version?: string;
        /**
         * Logo(base64)
         */
        logo?: string;
        /**
         * 作者
         */
        author?: string;
        /**
         * 状态 0-禁用 1-启用
         */
        status?: number;
        /**
         * 内容
         */
        content?: any;
        /**
         * ts内容
         */
        tsContent?: any;
        /**
         * 插件的plugin.json
         */
        pluginJson?: any;
        /**
         * 配置
         */
        config?: any;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PushAuditlogEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 日志时间
         */
        logTime?: Date;
        /**
         * 推送类型
         */
        pushType?: string;
        /**
         * 客户端ID
         */
        deviceNo?: string;
        /**
         * 原始请求内容
         */
        requestContent?: string;
        /**
         * 错误返回信息
         */
        errorMessage?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PushCameraGpsEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 设备序列号
         */
        sn?: string;
        /**
         * 消息序列号
         */
        msgSeq?: string;
        /**
         * 消息创建时间
         */
        createDate?: Date;
        /**
         * GPS定位时间
         */
        gpsTime?: Date;
        /**
         * 经度
         */
        longitude?: number;
        /**
         * 纬度
         */
        latitude?: number;
        /**
         * 速度
         */
        speed?: number;
        /**
         * 方向角
         */
        direction?: number;
        /**
         * 海拔高度
         */
        elevation?: number;
        /**
         * 定位状态
         */
        status?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PushSiteweightEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 站点编号
         */
        businessId?: string;
        /**
         * 归属单位
         */
        departmentId?: number;
        /**
         * 设备号
         */
        deviceNo?: string;
        /**
         * 上传时间
         */
        uploadTime?: Date;
        /**
         * 垃圾类型
         */
        garbageType?: string;
        /**
         * 垃圾重量
         */
        weight?: number;
        /**
         * 其他数据
         */
        extraData?: string;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 部门名称
         */
        departmentName?: string;
        /**
         * 名称
         */
        garbageName?: string;
        /**
         * 名称
         */
        businessName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PushVehicleGpsEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 设备序列号
         */
        sn?: string;
        /**
         * 消息序列号
         */
        msgSeq?: string;
        /**
         * 消息创建时间
         */
        createDate?: Date;
        /**
         * 车牌号
         */
        vehicleNo?: string;
        /**
         * GPS定位时间
         */
        gpsTime?: Date;
        /**
         * 经度
         */
        longitude?: number;
        /**
         * 纬度
         */
        latitude?: number;
        /**
         * 速度
         */
        speed?: number;
        /**
         * 方向角
         */
        direction?: number;
        /**
         * 海拔高度
         */
        elevation?: number;
        /**
         * 定位状态
         */
        status?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface PushWeightEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 设备号
         */
        deviceNo?: string;
        /**
         * 上传时间
         */
        uploadTime?: Date;
        /**
         * 垃圾类型
         */
        garbageType?: string;
        /**
         * 垃圾重量
         */
        weight?: number;
        /**
         * 其他数据
         */
        extraData?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface RecycleDataEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 表
         */
        entityInfo?: any;
        /**
         * 操作人
         */
        userId?: number;
        /**
         * 被删除的数据
         */
        data?: any;
        /**
         * 请求的接口
         */
        url?: string;
        /**
         * 请求参数
         */
        params?: any;
        /**
         * 删除数据条数
         */
        count?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 姓名
         */
        userName?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface SpaceInfoEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 地址
         */
        url?: string;
        /**
         * 类型
         */
        type?: string;
        /**
         * 分类ID
         */
        classifyId?: number;
        /**
         * 文件id
         */
        fileId?: string;
        /**
         * 文件名
         */
        name?: string;
        /**
         * 文件大小
         */
        size?: number;
        /**
         * 文档版本
         */
        version?: number;
        /**
         * 文件位置
         */
        key?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface SpaceTypeEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 类别名称
         */
        name?: string;
        /**
         * 父分类ID
         */
        parentId?: number;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface TaskInfoEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 任务ID
         */
        jobId?: string;
        /**
         * 任务配置
         */
        repeatConf?: string;
        /**
         * 名称
         */
        name?: string;
        /**
         * cron
         */
        cron?: string;
        /**
         * 最大执行次数 不传为无限次
         */
        limit?: number;
        /**
         * 每间隔多少毫秒执行一次 如果cron设置了 这项设置就无效
         */
        every?: number;
        /**
         * 备注
         */
        remark?: string;
        /**
         * 状态 0-停止 1-运行
         */
        status?: number;
        /**
         * 开始时间
         */
        startDate?: Date;
        /**
         * 结束时间
         */
        endDate?: Date;
        /**
         * 数据
         */
        data?: string;
        /**
         * 执行的service实例ID
         */
        service?: string;
        /**
         * 状态 0-系统 1-用户
         */
        type?: number;
        /**
         * 下一次执行时间
         */
        nextRunTime?: Date;
        /**
         * 状态 0-cron 1-时间间隔
         */
        taskType?: number;
        /**
         * undefined
         */
        lastExecuteTime?: Date;
        /**
         * undefined
         */
        lockExpireTime?: Date;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface UserAddressEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 用户ID
         */
        userId?: number;
        /**
         * 联系人
         */
        contact?: string;
        /**
         * 手机号
         */
        phone?: string;
        /**
         * 省
         */
        province?: string;
        /**
         * 市
         */
        city?: string;
        /**
         * 区
         */
        district?: string;
        /**
         * 地址
         */
        address?: string;
        /**
         * 是否默认
         */
        isDefault?: boolean;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    interface UserInfoEntity {
        /**
         * ID
         */
        id?: number;
        /**
         * 登录唯一ID
         */
        unionid?: string;
        /**
         * 头像
         */
        avatarUrl?: string;
        /**
         * 昵称
         */
        nickName?: string;
        /**
         * 手机号
         */
        phone?: string;
        /**
         * 性别
         */
        gender?: number;
        /**
         * 状态
         */
        status?: number;
        /**
         * 登录方式
         */
        loginType?: number;
        /**
         * 密码
         */
        password?: string;
        /**
         * 介绍
         */
        description?: string;
        /**
         * 创建时间
         */
        createTime?: string;
        /**
         * 更新时间
         */
        updateTime?: string;
        /**
         * 任意键值
         */
        [key: string]: any;
    }
    type json = any;
    interface PagePagination {
        size: number;
        page: number;
        total: number;
        [key: string]: any;
    }
    interface PageResponse<T> {
        pagination: PagePagination;
        list: T[];
        [key: string]: any;
    }
    interface BaseSysLogPageResponse {
        pagination: PagePagination;
        list: BaseSysLogEntity[];
    }
    interface BaseSysMenuPageResponse {
        pagination: PagePagination;
        list: BaseSysMenuEntity[];
    }
    interface BaseSysParamPageResponse {
        pagination: PagePagination;
        list: BaseSysParamEntity[];
    }
    interface BaseSysRolePageResponse {
        pagination: PagePagination;
        list: BaseSysRoleEntity[];
    }
    interface BaseSysUserPageResponse {
        pagination: PagePagination;
        list: BaseSysUserEntity[];
    }
    interface BasicdataDriverPageResponse {
        pagination: PagePagination;
        list: BasicdataDriverEntity[];
    }
    interface BasicdataIotPageResponse {
        pagination: PagePagination;
        list: BasicdataIotEntity[];
    }
    interface BasicdataSitePageResponse {
        pagination: PagePagination;
        list: BasicdataSiteEntity[];
    }
    interface BasicdataSorterPageResponse {
        pagination: PagePagination;
        list: BasicdataSorterEntity[];
    }
    interface BasicdataVehiclePageResponse {
        pagination: PagePagination;
        list: BasicdataVehicleEntity[];
    }
    interface BasicdataWastetypePageResponse {
        pagination: PagePagination;
        list: BasicdataWastetypeEntity[];
    }
    interface DemoGoodsPageResponse {
        pagination: PagePagination;
        list: DemoGoodsEntity[];
    }
    interface DictInfoPageResponse {
        pagination: PagePagination;
        list: DictInfoEntity[];
    }
    interface DictTypePageResponse {
        pagination: PagePagination;
        list: DictTypeEntity[];
    }
    interface PluginInfoPageResponse {
        pagination: PagePagination;
        list: PluginInfoEntity[];
    }
    interface PushAuditlogPageResponse {
        pagination: PagePagination;
        list: PushAuditlogEntity[];
    }
    interface PushSiteweightPageResponse {
        pagination: PagePagination;
        list: PushSiteweightEntity[];
    }
    interface PushWeightPageResponse {
        pagination: PagePagination;
        list: PushWeightEntity[];
    }
    interface PushCameragpsPageResponse {
        pagination: PagePagination;
        list: PushCameraGpsEntity[];
    }
    interface PushVehiclegpsPageResponse {
        pagination: PagePagination;
        list: PushVehicleGpsEntity[];
    }
    interface RecycleDataPageResponse {
        pagination: PagePagination;
        list: RecycleDataEntity[];
    }
    interface SpaceInfoPageResponse {
        pagination: PagePagination;
        list: SpaceInfoEntity[];
    }
    interface SpaceTypePageResponse {
        pagination: PagePagination;
        list: SpaceTypeEntity[];
    }
    interface TaskInfoPageResponse {
        pagination: PagePagination;
        list: TaskInfoEntity[];
    }
    interface UserAddressPageResponse {
        pagination: PagePagination;
        list: UserAddressEntity[];
    }
    interface UserInfoPageResponse {
        pagination: PagePagination;
        list: UserInfoEntity[];
    }
    interface BaseCoding {
        /**
         * 获取模块目录结构
         */
        getModuleTree(data?: any): Promise<any>;
        /**
         * 创建代码
         */
        createCode(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: { getModuleTree: string; createCode: string };
        /**
         * 权限状态
         */
        _permission: { getModuleTree: boolean; createCode: boolean };
        request: Request;
    }
    interface BaseComm {
        /**
         * 修改个人信息
         */
        personUpdate(data?: any): Promise<any>;
        /**
         * 文件上传模式
         */
        uploadMode(data?: any): Promise<any>;
        /**
         * 权限与菜单
         */
        permmenu(data?: any): Promise<any>;
        /**
         * 编程
         */
        program(data?: any): Promise<any>;
        /**
         * 个人信息
         */
        person(data?: any): Promise<any>;
        /**
         * 文件上传
         */
        upload(data?: any): Promise<any>;
        /**
         * 退出
         */
        logout(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            personUpdate: string;
            uploadMode: string;
            permmenu: string;
            program: string;
            person: string;
            upload: string;
            logout: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            personUpdate: boolean;
            uploadMode: boolean;
            permmenu: boolean;
            program: boolean;
            person: boolean;
            upload: boolean;
            logout: boolean;
        };
        request: Request;
    }
    interface BaseOpen {
        /**
         * 刷新token
         */
        refreshToken(data?: any): Promise<any>;
        /**
         * 验证码
         */
        captcha(data?: any): Promise<any>;
        /**
         * 登录
         */
        login(data?: any): Promise<any>;
        /**
         * 获得网页内容的参数值
         */
        html(data?: any): Promise<any>;
        /**
         * 实体信息与路径
         */
        eps(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            refreshToken: string;
            captcha: string;
            login: string;
            html: string;
            eps: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            refreshToken: boolean;
            captcha: boolean;
            login: boolean;
            html: boolean;
            eps: boolean;
        };
        request: Request;
    }
    interface BaseSysDepartment {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 排序
         */
        order(data?: any): Promise<any>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BaseSysDepartmentEntity[]>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: { delete: string; update: string; order: string; list: string; add: string };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            order: boolean;
            list: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BaseSysLog {
        /**
         * 日志保存时间
         */
        setKeep(data?: any): Promise<any>;
        /**
         * 获得日志保存时间
         */
        getKeep(data?: any): Promise<any>;
        /**
         * 清理
         */
        clear(data?: any): Promise<any>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BaseSysLogPageResponse>;
        /**
         * 权限标识
         */
        permission: { setKeep: string; getKeep: string; clear: string; page: string };
        /**
         * 权限状态
         */
        _permission: { setKeep: boolean; getKeep: boolean; clear: boolean; page: boolean };
        request: Request;
    }
    interface BaseSysMenu {
        /**
         * 创建代码
         */
        create(data?: any): Promise<any>;
        /**
         * 导出
         */
        export(data?: any): Promise<any>;
        /**
         * 导入
         */
        import(data?: any): Promise<any>;
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 解析
         */
        parse(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BaseSysMenuEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BaseSysMenuEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BaseSysMenuPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            create: string;
            export: string;
            import: string;
            delete: string;
            update: string;
            parse: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            create: boolean;
            export: boolean;
            import: boolean;
            delete: boolean;
            update: boolean;
            parse: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BaseSysParam {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 获得网页内容的参数值
         */
        html(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BaseSysParamEntity>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BaseSysParamPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            html: string;
            info: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            html: boolean;
            info: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BaseSysRole {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BaseSysRoleEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BaseSysRoleEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BaseSysRolePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BaseSysUser {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 移动部门
         */
        move(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BaseSysUserEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BaseSysUserEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BaseSysUserPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            move: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            move: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataDriver {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataDriverEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataDriverEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataDriverPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataIot {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataIotEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataIotEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataIotPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataSite {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataSiteEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataSiteEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataSitePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataSorter {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataSorterEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataSorterEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataSorterPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataVehicle {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataVehicleEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataVehicleEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataVehiclePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface BasicdataWastetype {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<BasicdataWastetypeEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<BasicdataWastetypeEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<BasicdataWastetypePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface DemoGoods {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<DemoGoodsEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<DemoGoodsEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<DemoGoodsPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface DemoTenant {
        /**
         * 局部不使用多租户
         */
        noTenant(data?: any): Promise<any>;
        /**
         * 不使用多租户
         */
        noUse(data?: any): Promise<any>;
        /**
         * use
         */
        use(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: { noTenant: string; noUse: string; use: string };
        /**
         * 权限状态
         */
        _permission: { noTenant: boolean; noUse: boolean; use: boolean };
        request: Request;
    }
    interface DictInfo {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 获得所有字典类型
         */
        types(data?: any): Promise<any>;
        /**
         * 获得字典数据
         */
        data(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<DictInfoEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<DictInfoEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<DictInfoPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            types: string;
            data: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            types: boolean;
            data: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface DictType {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<DictTypeEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<DictTypeEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<DictTypePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PluginInfo {
        /**
         * 安装插件
         */
        install(data?: any): Promise<any>;
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PluginInfoEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PluginInfoEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PluginInfoPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            install: string;
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            install: boolean;
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PushAuditlog {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PushAuditlogEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PushAuditlogEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PushAuditlogPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PushSiteweight {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PushSiteweightEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PushSiteweightEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PushSiteweightPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PushWeight {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PushWeightEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PushWeightEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PushWeightPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PushCameragps {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PushCameraGpsEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PushCameraGpsEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PushCameragpsPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface PushVehiclegps {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<PushVehicleGpsEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<PushVehicleGpsEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<PushVehiclegpsPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface RecycleData {
        /**
         * 恢复数据
         */
        restore(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<RecycleDataEntity>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<RecycleDataPageResponse>;
        /**
         * 权限标识
         */
        permission: { restore: string; info: string; page: string };
        /**
         * 权限状态
         */
        _permission: { restore: boolean; info: boolean; page: boolean };
        request: Request;
    }
    interface SpaceInfo {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<SpaceInfoEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<SpaceInfoEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<SpaceInfoPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface SpaceType {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<SpaceTypeEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<SpaceTypeEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<SpaceTypePageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface TaskInfo {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 开始
         */
        start(data?: any): Promise<any>;
        /**
         * 执行一次
         */
        once(data?: any): Promise<any>;
        /**
         * 停止
         */
        stop(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<TaskInfoEntity>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<TaskInfoPageResponse>;
        /**
         * 日志
         */
        log(data?: any): Promise<any>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            start: string;
            once: string;
            stop: string;
            info: string;
            page: string;
            log: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            start: boolean;
            once: boolean;
            stop: boolean;
            info: boolean;
            page: boolean;
            log: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface UserAddress {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<UserAddressEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<UserAddressEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<UserAddressPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface UserInfo {
        /**
         * 删除
         */
        delete(data?: any): Promise<any>;
        /**
         * 修改
         */
        update(data?: any): Promise<any>;
        /**
         * 单个信息
         */
        info(data?: any): Promise<UserInfoEntity>;
        /**
         * 列表查询
         */
        list(data?: any): Promise<UserInfoEntity[]>;
        /**
         * 分页查询
         */
        page(data?: any): Promise<UserInfoPageResponse>;
        /**
         * 新增
         */
        add(data?: any): Promise<any>;
        /**
         * 权限标识
         */
        permission: {
            delete: string;
            update: string;
            info: string;
            list: string;
            page: string;
            add: string;
        };
        /**
         * 权限状态
         */
        _permission: {
            delete: boolean;
            update: boolean;
            info: boolean;
            list: boolean;
            page: boolean;
            add: boolean;
        };
        request: Request;
    }
    interface RequestOptions {
        url: string;
        method?: "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT";
        data?: any;
        params?: any;
        headers?: any;
        timeout?: number;
        [key: string]: any;
    }
    type Request = (options: RequestOptions) => Promise<any>;
    type DictKey =
        | "ratedpayload"
        | "iottype"
        | "sitetype"
        | "datachanel"
        | "vehicletype"
        | "businesstype";
    type Service = {
        request: Request;
        base: {
            coding: BaseCoding;
            comm: BaseComm;
            open: BaseOpen;
            sys: {
                department: BaseSysDepartment;
                log: BaseSysLog;
                menu: BaseSysMenu;
                param: BaseSysParam;
                role: BaseSysRole;
                user: BaseSysUser;
            };
        };
        basicdata: {
            driver: BasicdataDriver;
            iot: BasicdataIot;
            site: BasicdataSite;
            sorter: BasicdataSorter;
            vehicle: BasicdataVehicle;
            wastetype: BasicdataWastetype;
        };
        demo: { goods: DemoGoods; tenant: DemoTenant };
        dict: { info: DictInfo; type: DictType };
        plugin: { info: PluginInfo };
        push: {
            auditlog: PushAuditlog;
            siteweight: PushSiteweight;
            weight: PushWeight;
            cameragps: PushCameragps;
            vehiclegps: PushVehiclegps;
        };
        recycle: { data: RecycleData };
        space: { info: SpaceInfo; type: SpaceType };
        task: { info: TaskInfo };
        user: { address: UserAddress; info: UserInfo };
    };
}
build/cool/eps.json
New file
@@ -0,0 +1 @@
[{"prefix":"/admin/base/coding","name":"","api":[{"method":"get","path":"/getModuleTree"},{"method":"post","path":"/createCode"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/base/comm","name":"","api":[{"method":"post","path":"/personUpdate"},{"method":"get","path":"/uploadMode"},{"method":"get","path":"/permmenu"},{"method":"get","path":"/program"},{"method":"get","path":"/person"},{"method":"post","path":"/upload"},{"method":"post","path":"/logout"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/base/open","name":"","api":[{"method":"get","path":"/refreshToken"},{"method":"get","path":"/captcha"},{"method":"post","path":"/login"},{"method":"get","path":"/html"},{"method":"get","path":"/eps"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/base/sys/department","name":"BaseSysDepartmentEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"post","path":"/order"},{"method":"post","path":"/list"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/base/sys/log","name":"BaseSysLogEntity","api":[{"method":"post","path":"/setKeep"},{"method":"get","path":"/getKeep"},{"method":"post","path":"/clear"},{"method":"post","path":"/page"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"name","type":"string","length":"","comment":"姓名","nullable":true,"source":"b.name"},{"propertyName":"action","type":"string","length":"","comment":"行为","nullable":false,"source":"a.action"},{"propertyName":"ip","type":"string","length":"","comment":"ip","nullable":true,"source":"a.ip"}]}},{"prefix":"/admin/base/sys/menu","name":"BaseSysMenuEntity","api":[{"method":"post","path":"/create"},{"method":"post","path":"/export"},{"method":"post","path":"/import"},{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"post","path":"/parse"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/base/sys/param","name":"BaseSysParamEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/html"},{"method":"get","path":"/info"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"dataType","type":"number","length":"","comment":"数据类型 0-字符串 1-富文本 2-文件 ","nullable":false,"defaultValue":0,"source":"a.dataType"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"name","type":"string","length":"","comment":"名称","nullable":false,"source":"a.name"},{"propertyName":"keyName","type":"string","length":"","comment":"键","nullable":false,"source":"a.keyName"}]}},{"prefix":"/admin/base/sys/role","name":"BaseSysRoleEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"name","type":"string","length":"","comment":"名称","nullable":false,"source":"a.name"},{"propertyName":"label","type":"string","length":"50","comment":"角色标签","nullable":true,"source":"a.label"}]}},{"prefix":"/admin/base/sys/user","name":"BaseSysUserEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"post","path":"/move"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/basicdata/driver","name":"BasicdataDriverEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"departmentId","type":"bigint","length":"","comment":"所属单位","nullable":true,"source":"a.departmentId"},{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":0,"dict":["激活","禁用"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"businessId","type":"string","length":"64","comment":"身份证号","nullable":false,"source":"a.businessId"},{"propertyName":"phone","type":"string","length":"32","comment":"手机号","nullable":true,"source":"a.phone"},{"propertyName":"licenseNo","type":"string","length":"64","comment":"驾驶证号","nullable":true,"source":"a.licenseNo"}]}},{"prefix":"/admin/basicdata/iot","name":"BasicdataIotEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"iotTypeCode","type":"string","length":"","comment":"终端类型","nullable":false,"source":"a.iotTypeCode"},{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":1,"dict":["禁用","启用"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"iotName","type":"string","length":"","comment":"终端名称","nullable":false,"source":"a.iotName"},{"propertyName":"iotCode","type":"string","length":"","comment":"终端编码","nullable":false,"source":"a.iotCode"}]}},{"prefix":"/admin/basicdata/site","name":"BasicdataSiteEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"departmentId","type":"number","length":"","comment":"归属单位","nullable":false,"source":"a.departmentId"},{"propertyName":"siteType","type":"string","length":"","comment":"站点类型","nullable":false,"defaultValue":"收集点","dict":"sitetype","source":"a.siteType"},{"propertyName":"status","type":"number","length":"","comment":"是否启用","nullable":false,"defaultValue":1,"dict":["禁用","启用"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"address","type":"string","length":"","comment":"地址","nullable":false,"source":"a.address"},{"propertyName":"leader","type":"string","length":"","comment":"负责人","nullable":false,"source":"a.leader"}]}},{"prefix":"/admin/basicdata/sorter","name":"BasicdataSorterEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":0,"dict":["在职","离职","禁用"],"source":"a.status"},{"propertyName":"departmentId","type":"bigint","length":"","comment":"所属单位","nullable":true,"source":"a.departmentId"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"businessName","type":"string","length":"64","comment":"姓名","nullable":false,"source":"a.businessName"},{"propertyName":"businessId","type":"string","length":"32","comment":"身份证号","nullable":true,"source":"a.businessId"},{"propertyName":"phone","type":"string","length":"32","comment":"手机号","nullable":true,"source":"a.phone"}]}},{"prefix":"/admin/basicdata/vehicle","name":"BasicdataVehicleEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"vehicleType","type":"string","length":"","comment":"车型","nullable":false,"defaultValue":"厢式货车","dict":"vehicletype","source":"a.vehicleType"},{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":0,"dict":["启用","禁用"],"source":"a.status"},{"propertyName":"departmentId","type":"bigint","length":"","comment":"所属单位","nullable":true,"source":"a.departmentId"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"businessId","type":"string","length":"32","comment":"车牌号","nullable":false,"source":"a.businessId"},{"propertyName":"defaultDriver","type":"bigint","length":"","comment":"默认司机","nullable":true,"source":"a.defaultDriver"}]}},{"prefix":"/admin/basicdata/wastetype","name":"BasicdataWastetypeEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"isRecyclable","type":"number","length":"","comment":"是否可回收","nullable":false,"defaultValue":0,"dict":["否","是"],"source":"a.isRecyclable"},{"propertyName":"status","type":"number","length":"","comment":"是否启用","nullable":false,"defaultValue":1,"dict":["禁用","启用"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"name","type":"string","length":"","comment":"名称","nullable":false,"source":"a.name"},{"propertyName":"code","type":"string","length":"","comment":"代码","nullable":false,"source":"a.code"}]}},{"prefix":"/admin/demo/goods","name":"DemoGoodsEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":1,"dict":["禁用","启用"],"source":"a.status"}],"fieldLike":[{"propertyName":"title","type":"string","length":"50","comment":"标题","nullable":false,"source":"a.title"}],"keyWordLikeFields":[{"propertyName":"description","type":"string","length":"","comment":"描述","nullable":true,"source":"a.description"}]}},{"prefix":"/admin/demo/tenant","name":"DemoGoodsEntity","api":[{"method":"post","path":"/noTenant"},{"method":"post","path":"/noUse"},{"method":"post","path":"/use"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/dict/info","name":"DictInfoEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/types"},{"method":"post","path":"/data"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/dict/type","name":"DictTypeEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/plugin/info","name":"PluginInfoEntity","api":[{"method":"post","path":"/install"},{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/push/auditlog","name":"PushAuditlogEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"pushType","type":"string","length":"","comment":"推送类型","nullable":false,"defaultValue":"weight","dict":["weight","health_gps","vehicle_gps","driver_anomaly","others"],"source":"a.pushType"}],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/push/cameragps","name":"PushCameraGpsEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"定位状态","nullable":false,"defaultValue":0,"dict":["有效定位","无效定位"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"sn","type":"string","length":"","comment":"设备序列号","nullable":false,"source":"a.sn"}]}},{"prefix":"/admin/push/siteweight","name":"PushSiteweightEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"deviceNo","type":"string","length":"","comment":"设备号","nullable":false,"source":"a.deviceNo"}],"fieldLike":[{"propertyName":"deviceNo","type":"string","length":"","comment":"设备号","nullable":false,"source":"a.deviceNo"}],"keyWordLikeFields":[]}},{"prefix":"/admin/push/vehiclegps","name":"PushVehicleGpsEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"定位状态","nullable":false,"defaultValue":0,"dict":["有效定位","无效定位"],"source":"a.status"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"sn","type":"string","length":"","comment":"设备序列号","nullable":false,"source":"a.sn"},{"propertyName":"vehicleNo","type":"string","length":"","comment":"车牌号","nullable":true,"source":"a.vehicleNo"}]}},{"prefix":"/admin/push/weight","name":"PushWeightEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"deviceNo","type":"string","length":"","comment":"设备号","nullable":false,"source":"a.deviceNo"},{"propertyName":"garbageType","type":"string","length":"","comment":"垃圾类型","nullable":false,"source":"a.garbageType"}],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/recycle/data","name":"RecycleDataEntity","api":[{"method":"post","path":"/restore"},{"method":"get","path":"/info"},{"method":"post","path":"/page"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"userName","type":"string","length":"","comment":"姓名","nullable":true,"source":"b.name"},{"propertyName":"url","type":"string","length":"","comment":"请求的接口","nullable":true,"source":"a.url"}]}},{"prefix":"/admin/space/info","name":"SpaceInfoEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"type","type":"string","length":"","comment":"类型","nullable":false,"source":"a.type"},{"propertyName":"classifyId","type":"number","length":"","comment":"分类ID","nullable":true,"source":"a.classifyId"}],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/space/type","name":"SpaceTypeEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/task/info","name":"TaskInfoEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"post","path":"/start"},{"method":"post","path":"/once"},{"method":"post","path":"/stop"},{"method":"get","path":"/info"},{"method":"post","path":"/page"},{"method":"get","path":"/log"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"状态 0-停止 1-运行","nullable":false,"defaultValue":1,"source":"a.status"},{"propertyName":"type","type":"number","length":"","comment":"状态 0-系统 1-用户","nullable":false,"defaultValue":0,"source":"a.type"}],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/user/address","name":"UserAddressEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[],"fieldLike":[],"keyWordLikeFields":[]}},{"prefix":"/admin/user/info","name":"UserInfoEntity","api":[{"method":"post","path":"/delete"},{"method":"post","path":"/update"},{"method":"get","path":"/info"},{"method":"post","path":"/list"},{"method":"post","path":"/page"},{"method":"post","path":"/add"}],"search":{"fieldEq":[{"propertyName":"status","type":"number","length":"","comment":"状态","nullable":false,"defaultValue":1,"dict":["禁用","正常","已注销"],"source":"a.status"},{"propertyName":"gender","type":"number","length":"","comment":"性别","nullable":false,"defaultValue":0,"dict":["未知","男","女"],"source":"a.gender"},{"propertyName":"loginType","type":"number","length":"","comment":"登录方式","nullable":false,"defaultValue":0,"dict":["小程序","公众号","H5"],"source":"a.loginType"}],"fieldLike":[],"keyWordLikeFields":[{"propertyName":"nickName","type":"string","length":"","comment":"昵称","nullable":true,"source":"a.nickName"},{"propertyName":"phone","type":"string","length":"","comment":"手机号","nullable":true,"source":"a.phone"}]}}]
env.d.ts
New file
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
    readonly VITE_NAME: string;
    readonly VITE_TIMEOUT: number;
}
interface ImportMeta {
    readonly env: ImportMetaEnv;
}
eslint.config.js
New file
@@ -0,0 +1,73 @@
import pluginVue from 'eslint-plugin-vue';
import vueTsEslintConfig from '@vue/eslint-config-typescript';
import prettier from 'eslint-plugin-prettier';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
export default [
    {
        name: 'app/files-to-lint',
        files: ['**/*.{ts,mts,tsx,vue}'],
    },
    {
        name: 'app/files-to-ignore',
        ignores: [
            '**/dist/**',
            '**/dist-ssr/**',
            '**/coverage/**',
            '**/packages/**',
            '**/build/**',
        ],
    },
    ...pluginVue.configs['flat/recommended'],
    ...vueTsEslintConfig(),
    skipFormatting,
    {
        languageOptions: {
            parserOptions: {
                ecmaVersion: 2020,
                ecmaFeatures: {
                    jsx: true,
                },
            },
        },
        rules: {
            '@typescript-eslint/ban-ts-ignore': 'off',
            '@typescript-eslint/explicit-function-return-type': 'off',
            '@typescript-eslint/no-explicit-any': 'off',
            '@typescript-eslint/no-var-requires': 'off',
            '@typescript-eslint/no-empty-function': 'off',
            '@typescript-eslint/no-use-before-define': 'off',
            '@typescript-eslint/ban-ts-comment': 'off',
            '@typescript-eslint/ban-types': 'off',
            '@typescript-eslint/no-non-null-assertion': 'off',
            '@typescript-eslint/explicit-module-boundary-types': 'off',
            '@typescript-eslint/no-namespace': 'off',
            '@typescript-eslint/no-unused-vars': 'off',
            '@typescript-eslint/no-empty-object-type': 'off',
            'space-before-function-paren': 'off',
            'no-unused-vars': 'off',
            'no-use-before-define': 'off',
            'no-self-assign': 'off',
            'vue/no-mutating-props': 'off',
            'vue/no-template-shadow': 'off',
            'vue/no-v-html': 'off',
            'vue/component-name-in-template-casing': ['error', 'kebab-case'],
            'vue/component-definition-name-casing': ['error', 'kebab-case'],
            'vue/attributes-order': 'off',
            'vue/one-component-per-file': 'off',
            'vue/html-closing-bracket-newline': 'off',
            'vue/max-attributes-per-line': 'off',
            'vue/multiline-html-element-content-newline': 'off',
            'vue/multi-word-component-names': 'off',
            'vue/singleline-html-element-content-newline': 'off',
            'vue/attribute-hyphenation': 'off',
            'vue/html-self-closing': 'off',
            'vue/require-default-prop': 'off',
            'vue/v-on-event-hyphenation': 'off',
            'vue/block-lang': 'off',
        },
    },
];
index.html
New file
@@ -0,0 +1,178 @@
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="referer" content="never" />
        <meta name="renderer" content="webkit" />
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
        />
        <title></title>
        <link rel="icon" href="./favicon.ico" />
        <style>
            html,
            body,
            #app {
                height: 100%;
                overflow: hidden;
            }
            * {
                -webkit-font-smoothing: antialiased;
                font-family:
                    'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
                    'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
            }
            .preload__wrap {
                display: flex;
                flex-direction: column;
                letter-spacing: 1px;
                background-color: #2f3447;
                position: fixed;
                left: 0;
                top: 0;
                height: 100%;
                width: 100%;
                z-index: 9999;
                transition: all 0.3s ease-in;
                opacity: 1;
                pointer-events: none;
            }
            .preload__wrap.is-hide {
                opacity: 0;
            }
            .preload__container {
                display: flex;
                justify-content: center;
                align-items: center;
                flex-direction: column;
                width: 100%;
                user-select: none;
                -webkit-user-select: none;
                flex-grow: 1;
            }
            .preload__name {
                font-size: 30px;
                color: #fff;
                letter-spacing: 5px;
                font-weight: bold;
                margin-bottom: 30px;
                min-height: 50px;
            }
            .preload__title {
                color: #fff;
                font-size: 14px;
                margin: 30px 0 20px 0;
                min-height: 20px;
            }
            .preload__sub-title {
                color: #ababab;
                font-size: 12px;
                min-height: 20px;
            }
            .preload__name,
            .preload__title,
            .preload__sub-title {
                animation: s 0.5s ease-in;
            }
            @keyframes s {
                from {
                    opacity: 0;
                }
                to {
                    opacity: 1;
                }
            }
            .preload__loading {
                height: 44px;
                width: 44px;
                border-radius: 30px;
                border: 7px solid currentColor;
                border-bottom-color: #2f3447;
                position: relative;
                animation:
                    r 1s infinite cubic-bezier(0.17, 0.67, 0.83, 0.67),
                    bc 2s infinite ease-in;
                transform: rotate(0deg);
                box-sizing: border-box;
            }
            @keyframes r {
                from {
                    transform: rotate(0deg);
                }
                to {
                    transform: rotate(360deg);
                }
            }
            .preload__loading::after,
            .preload__loading::before {
                content: '';
                display: inline-block;
                position: absolute;
                bottom: -2px;
                height: 7px;
                width: 7px;
                border-radius: 10px;
                background-color: currentColor;
            }
            .preload__loading::after {
                left: -1px;
            }
            .preload__loading::before {
                right: -1px;
            }
            @keyframes bc {
                0% {
                    color: #689cc5;
                }
                25% {
                    color: #b3b7e2;
                }
                50% {
                    color: #93dbe9;
                }
                75% {
                    color: #abbd81;
                }
                100% {
                    color: #689cc5;
                }
            }
        </style>
    </head>
    <body>
        <div class="preload__wrap" id="Loading">
            <div class="preload__container">
                <div class="preload__name"></div>
                <div class="preload__loading"></div>
                <div class="preload__title"></div>
                <div class="preload__sub-title"></div>
            </div>
        </div>
        <div id="app"></div>
        <script type="module" src="/src/main.ts"></script>
    </body>
</html>
nginx.conf
New file
@@ -0,0 +1,72 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
  worker_connections 1024;
}
http {
  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  '$status $body_bytes_sent "$http_referer" '
  '"$http_user_agent" "$http_x_forwarded_for"';
  access_log /var/log/nginx/access.log main;
  sendfile on;
  keepalive_timeout 65;
  upstream cool {
    server midway:8001;
  }
  server {
    listen 80;
    server_name localhost;
    location / {
      root /app;
      index index.html;
      try_files $uri $uri/ /index.html;
    }
    location /api/ {
      proxy_pass http://cool/;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header REMOTE-HOST $remote_addr;
      #缓存相关配置
      #proxy_cache cache_one;
      #proxy_cache_key $host$request_uri$is_args$args;
      #proxy_cache_valid 200 304 301 302 1h;
      #持久化连接相关配置
      proxy_connect_timeout 3000s;
      proxy_read_timeout 86400s;
      proxy_send_timeout 3000s;
      #proxy_http_version 1.1;
      #proxy_set_header Upgrade $http_upgrade;
      #proxy_set_header Connection "upgrade";
      add_header X-Cache $upstream_cache_status;
      #expires 12h;
    }
    # socket需额外配置
    location /socket {
      proxy_pass http://cool/socket;
      proxy_connect_timeout 3600s; #配置点1
      proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
      proxy_send_timeout 3600s; #配置点3
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header REMOTE-HOST $remote_addr;
      #proxy_bind $remote_addr transparent;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      rewrite /socket/(.*) /$1 break;
      proxy_redirect off;
    }
  }
}
package.json
New file
@@ -0,0 +1,75 @@
{
    "name": "cool-admin-vue",
    "version": "8.0.0",
    "type": "module",
    "scripts": {
        "dev": "vite --host",
        "build": "vite build",
        "build-static": "vite build --mode static",
        "build-demo": "vite build --mode demo",
        "preview": "vite preview",
        "type-check": "vue-tsc --build --force",
        "lint": "eslint . --fix",
        "format": "prettier --write src/"
    },
    "dependencies": {
        "@cool-vue/crud": "^8.0.6",
        "@element-plus/icons-vue": "^2.3.1",
        "@vueuse/core": "^12.5.0",
        "@wangeditor/editor": "^5.1.23",
        "@wangeditor/editor-for-vue": "^5.1.12",
        "axios": "^1.7.9",
        "chardet": "^2.0.0",
        "core-js": "^3.40.0",
        "dayjs": "^1.11.13",
        "echarts": "^5.6.0",
        "element-plus": "2.10.2",
        "file-saver": "^2.0.5",
        "lodash-es": "^4.17.21",
        "marked": "^14.1.3",
        "mitt": "^3.0.1",
        "nprogress": "^0.2.0",
        "pinia": "^2.3.1",
        "store": "^2.0.12",
        "vue": "^3.5.13",
        "vue-echarts": "^7.0.3",
        "vue-i18n": "^11.0.1",
        "vue-router": "^4.5.0",
        "vuedraggable": "^4.1.0",
        "xlsx": "^0.18.5"
    },
    "devDependencies": {
        "@cool-vue/vite-plugin": "^8.2.2",
        "@intlify/unplugin-vue-i18n": "^6.0.3",
        "@rushstack/eslint-patch": "^1.10.5",
        "@tsconfig/node20": "^20.1.4",
        "@types/file-saver": "^2.0.7",
        "@types/lodash-es": "^4.17.12",
        "@types/mockjs": "^1.0.10",
        "@types/node": "^20.17.17",
        "@types/nprogress": "^0.2.3",
        "@types/store": "^2.0.5",
        "@vitejs/plugin-vue": "^5.2.1",
        "@vitejs/plugin-vue-jsx": "^4.1.1",
        "@vue/compiler-sfc": "^3.5.13",
        "@vue/eslint-config-prettier": "^10.2.0",
        "@vue/eslint-config-typescript": "^14.3.0",
        "@vue/test-utils": "^2.4.6",
        "@vue/tsconfig": "^0.5.1",
        "autoprefixer": "^10.4.20",
        "eslint": "^9.19.0",
        "eslint-plugin-prettier": "^5.2.3",
        "eslint-plugin-vue": "^9.32.0",
        "postcss": "^8.5.1",
        "prettier": "^3.4.2",
        "rollup-plugin-visualizer": "^5.14.0",
        "sass": "1.81.0",
        "tailwindcss": "^3.4.17",
        "terser": "^5.36.0",
        "typescript": "~5.5.4",
        "vite": "^5.4.14",
        "vite-plugin-compression": "^0.5.1",
        "vite-plugin-vue-devtools": "^7.7.1",
        "vue-tsc": "^2.2.0"
    }
}
packages/crud/.gitignore
New file
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
packages/crud/.prettierrc
New file
@@ -0,0 +1,9 @@
{
    "tabWidth": 4,
    "useTabs": true,
    "semi": true,
    "jsxBracketSameLine": true,
    "singleQuote": false,
    "printWidth": 100,
    "trailingComma": "none"
}
packages/crud/README.md
New file
@@ -0,0 +1,33 @@
# 介绍
**cool-admin for vue**是基于[Vue.js](https://v3.cn.vuejs.org)开发。
[cool-admin 官方文档](https://cool-js.com/)
尝试 `cool-admin` 最简单的方法就是查看文档及运行示例。
<img src='https://vue.cool-admin.com/show/admin.png' />
[Ai极速编码 🔥 在线体验](https://show.cool-admin.com/helper/ai-code)
<img src='https://vue.cool-admin.com/show/code.png' />
## 代码仓库
**cool-admin for vue** 是开源免费的,遵循[MIT](https://baike.baidu.com/item/MIT/10772952)开源协议,意味着您无需支付任何费用,也无需授权,即可将它应用到您的产品中。
开源免费,并不意味着您可以将 cool-admin 应用到非法的领域,比如涉及赌博,暴力等方面。如因此产生纠纷等法律问题,`cool-admin`不承担任何责任。
[https://github.com/cool-team-official/cool-admin-vue](https://github.com/cool-team-official/cool-admin-vue)
```shell
git clone https://github.com/cool-team-official/cool-admin-vue.git
```
## 技术选型
-   [Vue.js](https://v3.cn.vuejs.org),基础框架;
-   [VueRouter](https://router.vuejs.org),Vue.js 官方路由;
-   [Pinia](https://pinia.vuejs.org),轻量级状态管理库;
-   [ElementPlus](https://element-plus.gitee.io/zh-CN),桌面端组件库;
-   [Vite](https://vitejs.cn),构建工具;
packages/crud/index.d.ts
New file
@@ -0,0 +1,811 @@
// vue
declare namespace Vue {
    interface Ref<T = any> {
        value: T;
    }
    type Emit = (name: any, ...args: any[]) => void;
}
// element-plus
declare namespace ElementPlus {
    type Size = "large" | "default" | "small";
    type Align = "left" | "center" | "right";
    interface FormProps {
        inline?: boolean;
        labelPosition?: "left" | "right" | "top";
        labelWidth?: string | number;
        labelSuffix?: string;
        hideRequiredAsterisk?: boolean;
        showMessage?: boolean;
        inlineMessage?: boolean;
        statusIcon?: boolean;
        validateOnRuleChange?: boolean;
        size?: Size;
        disabled?: boolean;
        [key: string]: any;
    }
}
// mitt
declare interface Mitt {
    on(name: string, callback: (data: any) => void): void;
    off(name: string, callback: (data: any) => void): void;
    emit(name: string, data?: any): void;
}
// emitter
declare interface EmitterItem {
    name: string;
    callback(data: any, events: { refresh(params: any): void; crudList: ClCrud.Ref[] }): void;
}
declare interface Emitter {
    list: EmitterItem[];
    init(events: any): void;
    on(name: string, callback: (data: any) => void): void;
    emit(name: string, data?: any): void;
}
// 方法
declare type fn = () => void;
// 对象
declare type obj = {
    [key: string]: any;
};
// 全部可选
declare type DeepPartial<T> = T extends Function
    ? T
    : T extends object
        ? { [P in keyof T]?: DeepPartial<T[P]> }
        : T;
// 合并
declare type Merge<A, B> = Omit<A, keyof B> & B;
// 移除 [key]
declare type RemoveIndex<T> = {
    [P in keyof T as string extends P ? never : number extends P ? never : P]: T[P];
};
// 任用列表
declare type List<T> = Array<DeepPartial<T> | (() => DeepPartial<T>)>;
// 获取keys
declare type PropKey<T> = keyof RemoveIndex<T> | (string & {});
// 任意字符串
declare type AnyString = string & {};
// 类型或者 Ref 泛型
declare type RefData<T = any> = T | Vue.Ref<T>;
// browser
declare type Browser = {
    screen: string;
    isMini: boolean;
};
// 字典选项
declare type DictOptions = {
    label?: string;
    value?: any;
    color?: string;
    type?: string;
    [key: string]: any;
}[];
// render
declare namespace Render {
    type OpButton =
        | `slot-${string}`
        | {
                label: string;
                type?: string;
                hidden?: boolean;
                onClick(options: { scope: obj }): void;
                [key: string]: any;
          };
    interface Props {
        onChange?(value: any): void;
        [key: string]: any;
    }
    interface Component {
        name?: string;
        options?: RefData<DictOptions>;
        props?: RefData<Props>;
        style?: obj;
        slots?: {
            [key: string]: (data?: any) => any;
        };
        vm?: any;
        [key: string]: any;
    }
}
// crud
declare namespace ClCrud {
    declare interface Field {
        comment: string;
        source: string;
        propertyName: string;
        type: string;
        dict: string | string[];
        nullable: boolean;
    }
    interface Label {
        op: string;
        add: string;
        delete: string;
        multiDelete: string;
        update: string;
        refresh: string;
        info: string;
        search: string;
        reset: string;
        clear: string;
        save: string;
        close: string;
        confirm: string;
        advSearch: string;
        searchKey: string;
        placeholder: string;
        placeholderSelect: string;
        tips: string;
        saveSuccess: string;
        deleteSuccess: string;
        deleteConfirm: string;
        empty: string;
        desc: string;
        asc: string;
        select: string;
        deselect: string;
        seeMore: string;
        hideContent: string;
        nonEmpty: string;
        collapse: string;
        expand: string;
        [key: string]: string;
    }
    interface Dict {
        primaryId: string;
        api: {
            list: string;
            add: string;
            update: string;
            delete: string;
            info: string;
            page: string;
        };
        pagination: {
            page: string;
            size: string;
        };
        search: {
            keyWord: string;
            query: string;
        };
        sort: {
            order: string;
            prop: string;
        };
        label: Label;
    }
    interface Permission {
        page?: boolean;
        list?: boolean;
        add?: boolean;
        delete?: boolean;
        update?: boolean;
        info?: boolean;
        [key: string]: any;
    }
    interface Params {
        page: {
            page?: number;
            size?: number;
            [key: string]: any;
        };
        list: obj;
        add: obj;
        delete: {
            ids?: any[];
            [key: string]: any;
        };
        update: {
            id?: any;
            [key: string]: any;
        };
        info: {
            id?: any;
            [key: string]: any;
        };
    }
    interface Response {
        page: {
            list: any[];
            pagination: {
                total: number;
                page: number;
                size: number;
                [key: string]: any;
            };
            [key: string]: any;
        };
        list: any[];
        add: any;
        update: any;
        info: any;
        delete: any;
    }
    interface Service {
        api: {
            page(params?: Params["page"]): Promise<Response["page"]>;
            list(params?: Params["list"]): Promise<Response["list"]>;
            add(params?: Params["add"]): Promise<Response["add"]>;
            update(params?: Params["update"]): Promise<Response["update"]>;
            info(params?: Params["info"]): Promise<Response["info"]>;
            delete(params?: Params["delete"]): Promise<Response["delete"]>;
            permission: Permission;
            search: {
                fieldEq: Field[];
                fieldLike: Field[];
                keyWordLikeFields: Field[];
            };
            [key: string]: any;
        };
    }
    interface Config {
        name: string;
        service: Service["api"];
        permission: Permission;
        dict: Dict;
        onRefresh(
            params: obj,
            event: {
                done: fn;
                next: Service["api"]["page"];
                render: (data: any | any[], pagination?: Response["page"]["pagination"]) => void;
            }
        ): void;
        onDelete(
            selection: obj[],
            event: {
                next: Service["api"]["delete"];
            }
        ): void;
    }
    interface Ref {
        "cl-table": ClTable.Ref;
        "cl-upsert": ClUpsert.Ref;
        id: number;
        mitt: Mitt;
        name: string;
        routePath: string;
        permission: Permission;
        dict: Dict;
        service: Service["api"];
        loading: boolean;
        params: obj;
        selection: obj[];
        set(key: "dict" | "style" | "service" | "permission", value: any): void;
        done(): void;
        getParams(): obj;
        setParams(data: obj): void;
        getPermission(key?: string): boolean;
        rowInfo(data: obj): void;
        rowAdd(): void;
        rowEdit(data: obj): void;
        rowAppend(data?: obj): void;
        rowClose(): void;
        rowDelete(...selection: obj[]): void;
        proxy(name: string, data?: any[]): any;
        paramsReplace(params: obj): obj;
        refresh: Service["api"]["page"];
        [key: string]: any;
    }
    interface Options extends DeepPartial<Config> {
        service?: any;
    }
}
declare namespace ClTable {
    type OpButton = Array<"info" | "edit" | "delete" | AnyString | Render.OpButton>;
    type ColumnType = "index" | "selection" | "expand" | "op" | AnyString;
    interface Column<T = any> {
        type: ColumnType;
        hidden: RefData<boolean>;
        component: Render.Component;
        search: {
            isInput: boolean;
            value: any;
            icon: () => any;
            refreshOnChange: boolean;
            component: Render.Component;
        };
        dict: RefData<DictOptions>;
        dictFormatter: (values: DictOptions) => string;
        dictColor: boolean;
        dictSeparator: string;
        dictAllLevels: boolean;
        buttons: OpButton | ((options: { scope: T }) => OpButton);
        align: ElementPlus.Align;
        label: any;
        renderLabel: (options: { column: any; $index: number }) => any;
        className: string;
        prop: PropKey<T>;
        orderNum: number;
        width: RefData<number | string>;
        minWidth: RefData<number | string>;
        renderHeader: (options: { column: any; $index: number }) => any;
        sortable: boolean | "desc" | "descending" | "ascending" | "asc" | "custom";
        sortMethod: fn;
        sortBy: string | ((row: T, index: number) => any) | any[];
        resizable: boolean;
        columnKey: string;
        headerAlign: ElementPlus.Align;
        showOverflowTooltip: boolean;
        fixed: boolean | string;
        render: (row: T, column: any, value: any, index: number) => any;
        formatter: (row: T, column: any, value: any, index: number) => any;
        selectable: (row: T, index: number) => boolean;
        reserveSelection: boolean;
        filterMethod: fn;
        filteredValue: unknown[];
        filters: unknown[];
        filterPlacement: string;
        filterMultiple: boolean;
        index: ((index: number) => number) | number;
        sortOrders: unknown[];
        children: Column<T>[];
        [key: string]: any;
    }
    type ContextMenu = Array<
        | ClContextMenu.Item
        | ((row: obj, column: obj, event: PointerEvent) => ClContextMenu.Item)
        | "refresh"
        | "check"
        | "update"
        | "edit"
        | "delete"
        | "info"
        | "order-desc"
        | "order-asc"
    >;
    type Plugin = (options: { exposed: Ref }) => void;
    interface Config<T = any> {
        columns: Column<T>[];
        autoHeight: boolean;
        height: any;
        contextMenu: ContextMenu;
        defaultSort: {
            prop: string;
            order: "descending" | "ascending";
        };
        sortRefresh: boolean;
        emptyText: string;
        rowKey: string;
        on?: {
            [key: string]: (...args: any[]) => void;
        };
        props?: {
            [key: string]: any;
        };
        plugins?: Plugin[];
        onRowContextmenu?(row: T, column: any, event: any): void;
    }
    interface Ref<T = any> {
        Table: any;
        config: obj;
        selection: T[];
        data: T[];
        columns: Column<T>[];
        reBuild(cb?: fn): void;
        calcMaxHeight(): void;
        setData(data: T[]): void;
        setColumns(columns: Column[]): void;
        showColumn(props: PropKey<T> | PropKey<T>[], status?: boolean): void;
        hideColumn(props: PropKey<T> | PropKey<T>[]): void;
        changeSort(prop: PropKey<T>, order: string): void;
        clearSelection(): void;
        getSelectionRows(): any[];
        toggleRowSelection(row: T, selected?: boolean): void;
        toggleAllSelection(): void;
        toggleRowExpansion(row: T, expanded?: boolean): void;
        setCurrentRow(row: T): void;
        clearSort(): void;
        clearFilter(columnKeys: PropKey<T>[]): void;
        doLayout(): void;
        sort(prop: PropKey<T>, order: string): void;
        scrollTo(position: { top?: number; left?: number }): void;
        setScrollTop(top: number): void;
        setScrollLeft(left: number): void;
        updateKeyChildren(key: string, children: any[]): void;
    }
    interface Options<T = any> extends DeepPartial<Config<T>> {
        columns?: List<ClTable.Column<T>>;
    }
}
declare namespace ClFormTabs {
    type labels = {
        label: string;
        value: string;
        name?: string;
        icon?: any;
        lazy?: boolean;
        [key: string]: any;
    }[];
}
declare namespace ClForm {
    type CloseAction = "close" | "save" | AnyString;
    interface Rule {
        type?:
            | "string"
            | "number"
            | "boolean"
            | "method"
            | "regexp"
            | "integer"
            | "float"
            | "array"
            | "object"
            | "enum"
            | "date"
            | "url"
            | "hex"
            | "email"
            | "any";
        required?: boolean;
        message?: string;
        min?: number;
        max?: number;
        trigger?: any;
        validator?(rule: any, value: any, callback: (error?: Error) => void): void;
        [key: string]: any;
    }
    interface Hook {
        Fn: (value: any, options: { form: obj; prop: string; method: "submit" | "bind" }) => any;
        Key:
            | "number"
            | "string"
            | "split"
            | "join"
            | "boolean"
            | "booleanNumber"
            | "datetimeRange"
            | "splitJoin"
            | "json"
            | "empty"
            | AnyString;
        Pipe: Hook["Key"] | Hook["Fn"];
        Event: {
            bind?: Hook["Pipe"] | Hook["Pipe"][];
            submit?: Hook["Pipe"] | Hook["Pipe"][];
            reset?: (prop: string) => string[];
        };
    }
    interface Item<T = any> {
        type?: "tabs";
        prop?: PropKey<T>;
        props?: {
            labels?: ClFormTabs.labels;
            justify?: "left" | "center" | "right";
            color?: string;
            mergeProp?: boolean;
            labelWidth?: string;
            error?: string;
            showMessage?: boolean;
            inlineMessage?: boolean;
            size?: "medium" | "default" | "small";
            [key: string]: any;
        };
        span?: number;
        col?: {
            span: number;
            offset: number;
            push: number;
            pull: number;
            xs: any;
            sm: any;
            md: any;
            lg: any;
            xl: any;
            tag: string;
        };
        group?: string;
        collapse?: boolean;
        value?: any;
        label?: string;
        renderLabel?: any;
        flex?: boolean;
        hook?: Hook["Event"] | Hook["Key"];
        hidden?: boolean | ((options: { scope: obj }) => boolean);
        prepend?: Render.Component;
        component?: Render.Component;
        append?: Render.Component;
        rules?: Rule | Rule[];
        required?: boolean;
        children?: Item[];
        [key: string]: any;
    }
    interface Config<T = any> {
        title?: any;
        height?: any;
        width?: any;
        props: ElementPlus.FormProps;
        items: Item[];
        form: obj;
        isReset?: boolean;
        on?: {
            open?(data: T): void;
            close?(action: CloseAction, done: fn): void;
            submit?(data: T, event: { close: fn; done: fn }): void;
            change?(data: T, prop: string): void;
        };
        op: {
            hidden?: boolean;
            saveButtonText?: string;
            closeButtonText?: string;
            justify?: "flex-start" | "center" | "flex-end";
            buttons?: Array<CloseAction | Render.OpButton>;
        };
        dialog: {
            title?: any;
            height?: string;
            width?: string;
            hideHeader?: boolean;
            controls?: Array<"fullscreen" | "close" | AnyString>;
            [key: string]: any;
        };
        [key: string]: any;
    }
    type Plugin = (options: {
        exposed: Ref;
        onOpen(cb: () => void): void;
        onClose(cb: () => void): void;
        onSubmit(cb: (data: obj) => obj): void;
    }) => void;
    type Items<T = any> = List<Item<T>>;
    interface Ref<T = any> {
        Form: any;
        form: T;
        config: {
            items: Item[];
            [key: string]: any;
        };
        open(options: Options<T>, plugins?: Plugin[]): void;
        close(action?: CloseAction): void;
        done(): void;
        clear(): void;
        reset(): void;
        showLoading(): void;
        hideLoading(): void;
        setDisabled(flag?: boolean): void;
        invokeData(data: any): void;
        setData(prop: string, value: any): void;
        bindForm(data: obj): void;
        getForm(prop?: string): any;
        setForm(prop: string, value: any): void;
        setOptions(prop: string, list: DictOptions): void;
        setProps(prop: string, value: any): void;
        setConfig(path: string, value: any): void;
        showItem(props: string[] | string): void;
        hideItem(props: string[] | string): void;
        toggleItem(prop: string, flag?: boolean): void;
        resetFields(): void;
        clearValidate(props?: string[] | string): void;
        validateField(
            props?: string[] | string,
            callback?: (isValid: boolean, invalidFields: any[]) => void
        ): Promise<void>;
        validate(callback: (isValid: boolean, invalidFields: any[]) => void): Promise<void>;
        changeTab(value: any, valid?: boolean): Promise<any>;
        setTitle(value: string): void;
        submit(cb?: (data: obj) => void): void;
        Tabs: {
            active: RefData<string>;
            list: ClFormTabs.labels;
            change(value: any, valid?: boolean): Promise<any>;
            [key: string]: any;
        };
        [key: string]: any;
    }
    interface Options<T = any> extends DeepPartial<Config> {
        items?: Items<T>;
    }
}
declare namespace ClUpsert {
    interface Config<T = any> {
        sync: boolean;
        items: ClForm.Item[];
        props: ClForm.Config["props"];
        op: ClForm.Config["op"];
        dialog: ClForm.Config["dialog"];
        onOpen?(): void;
        onOpened?(data: T): void;
        onClose?(action: ClForm.CloseAction, done: fn): void;
        onClosed?(): void;
        onInfo?(
            data: T,
            event: { close: fn; done(data: T): void; next: ClCrud.Service["api"]["info"] }
        ): void;
        onSubmit?(
            data: T,
            event: { close: fn; done: fn; next: ClCrud.Service["api"]["update"] }
        ): void;
        plugins?: ClForm.Plugin[];
    }
    interface Ref<T = any> extends ClForm.Ref<T> {
        mode: "add" | "update" | "info" | AnyString;
    }
    interface Options<T = any> extends DeepPartial<Config<T>> {
        items?: ClForm.Items<T>;
    }
}
declare namespace ClAdvSearch {
    interface Config<T = any> {
        items?: ClForm.Item[];
        title?: string;
        size?: string | number;
        op?: ("clear" | "reset" | "close" | "search" | `slot-${string}`)[];
        onSearch?(data: T, options: { next: ClCrud.Service["api"]["page"]; close(): void }): void;
    }
    interface Ref<T = any> extends ClForm.Ref<T> {}
    interface Options<T = any> extends DeepPartial<Config<T>> {
        items?: ClForm.Items<T>;
    }
}
declare namespace ClSearch {
    type Plugin = (options: { exposed: Ref }) => void;
    interface Config<T = any> {
        inline?: boolean;
        items?: ClForm.Item[];
        data?: T;
        props?: ElementPlus.FormProps;
        resetBtn?: boolean;
        collapse?: boolean;
        Form?: ClForm.Ref;
        onChange?(data: T, prop: string): void;
        onLoad?(data: T): void;
        onSearch?(data: T, options: { next: ClCrud.Service["api"]["page"] }): void;
        plugins?: Plugin[];
    }
    interface Ref<T = any> extends ClForm.Ref<T> {
        search(params?: obj): void;
        reset(): void;
    }
    interface Options<T = any> extends DeepPartial<Config<T>> {
        items?: ClForm.Items<T>;
    }
}
declare namespace ClContextMenu {
    interface Item {
        label: string;
        prefixIcon?: any;
        suffixIcon?: any;
        ellipsis?: boolean;
        disabled?: boolean;
        hidden?: boolean;
        children?: Item[];
        showChildren?: boolean;
        callback?(done: fn): void;
        [key: string]: any;
    }
    interface Event {
        pageX: number;
        pageY: number;
        [key: string]: any;
    }
    interface Options {
        class?: string;
        hover?:
            | boolean
            | {
                    target?: string;
                    className?: string;
              };
        list?: Item[];
    }
    interface Ref {
        open(event: Event, options: Options): Exposed;
        close(): void;
    }
    interface Exposed {
        close(): void;
    }
}
declare namespace ClDialog {
    interface Provide {
        visible: Vue.Ref<boolean>;
        fullscreen: Vue.Ref<boolean>;
    }
}
declare interface Config {
    dict: ClCrud.Dict;
    permission: ClCrud.Permission;
    events: {
        [key: string]: (...args: any[]) => any;
    };
    style: {
        size: ElementPlus.Size;
        colors: string[];
        form: {
            labelPosition: ElementPlus.FormProps["labelPosition"];
            labelWidth: ElementPlus.FormProps["labelWidth"];
            span: number;
            plugins: ClForm.Plugin[];
        };
        table: {
            stripe: boolean;
            border: boolean;
            highlightCurrentRow: boolean;
            resizable: boolean;
            autoHeight: boolean;
            contextMenu: ClTable.ContextMenu;
            column: {
                minWidth: number | string;
                align: ElementPlus.Align;
                headerAlign: ElementPlus.Align;
                opWidth: number | string;
            };
            plugins: ClTable.Plugin[];
        };
        search: {
            plugins: ClSearch.Plugin[];
        };
    };
}
declare type Options = DeepPartial<Config>;
declare interface CrudOptions {
    options: Options;
}
packages/crud/index.html
New file
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="referer" content="never" />
        <meta name="renderer" content="webkit" />
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
        />
        <title></title>
        <link rel="icon" href="./favicon.ico" />
    </head>
    <body>
        <div class="preload__wrap" id="Loading">
            <div class="preload__container">
                <p class="preload__name"></p>
                <div class="preload__loading"></div>
                <p class="preload__title"></p>
                <p class="preload__sub-title"></p>
            </div>
        </div>
        <div id="app"></div>
        <script type="module" src="/src/main.ts"></script>
    </body>
</html>
packages/crud/package.json
New file
@@ -0,0 +1,38 @@
{
    "name": "@cool-vue/crud",
    "version": "8.0.6",
    "private": false,
    "main": "./dist/index.umd.js",
    "module": "./dist/index.es.js",
    "types": "types/entry.d.ts",
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vue-tsc && vite build",
        "preview": "vite preview"
    },
    "dependencies": {
        "@vue/runtime-core": "^3.5.13",
        "element-plus": "^2.10.4",
        "lodash-es": "^4.17.21",
        "vue": "^3.5.13"
    },
    "devDependencies": {
        "@types/node": "^20.11.16",
        "@vitejs/plugin-vue": "^5.2.1",
        "@vitejs/plugin-vue-jsx": "^4.1.1",
        "prettier": "^3.5.1",
        "sass": "^1.85.0",
        "sass-loader": "^16.0.5",
        "typescript": "^5.3.3",
        "vite": "^6.1.0",
        "vite-plugin-dts": "^4.5.0",
        "vue-tsc": "^2.2.2"
    },
    "files": [
        "types",
        "dist",
        "index.d.ts",
        "index.ts"
    ]
}
packages/crud/pnpm-lock.yaml
New file
@@ -0,0 +1,2350 @@
lockfileVersion: '9.0'
settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false
importers:
  .:
    dependencies:
      '@vue/runtime-core':
        specifier: ^3.5.13
        version: 3.5.13
      element-plus:
        specifier: ^2.9.4
        version: 2.9.4(vue@3.5.13(typescript@5.7.3))
      lodash-es:
        specifier: ^4.17.21
        version: 4.17.21
      vue:
        specifier: ^3.5.13
        version: 3.5.13(typescript@5.7.3)
    devDependencies:
      '@types/node':
        specifier: ^20.11.16
        version: 20.17.19
      '@vitejs/plugin-vue':
        specifier: ^5.2.1
        version: 5.2.1(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3))
      '@vitejs/plugin-vue-jsx':
        specifier: ^4.1.1
        version: 4.1.1(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3))
      prettier:
        specifier: ^3.5.1
        version: 3.5.1
      sass:
        specifier: ^1.85.0
        version: 1.85.0
      sass-loader:
        specifier: ^16.0.5
        version: 16.0.5(sass@1.85.0)
      typescript:
        specifier: ^5.3.3
        version: 5.7.3
      vite:
        specifier: ^6.1.0
        version: 6.1.0(@types/node@20.17.19)(sass@1.85.0)
      vite-plugin-dts:
        specifier: ^4.5.0
        version: 4.5.0(@types/node@20.17.19)(rollup@4.34.8)(typescript@5.7.3)(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0))
      vue-tsc:
        specifier: ^2.2.2
        version: 2.2.2(typescript@5.7.3)
packages:
  '@ampproject/remapping@2.3.0':
    resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
    engines: {node: '>=6.0.0'}
  '@babel/code-frame@7.26.2':
    resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
    engines: {node: '>=6.9.0'}
  '@babel/compat-data@7.26.8':
    resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==}
    engines: {node: '>=6.9.0'}
  '@babel/core@7.26.9':
    resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==}
    engines: {node: '>=6.9.0'}
  '@babel/generator@7.26.9':
    resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-annotate-as-pure@7.25.9':
    resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-compilation-targets@7.26.5':
    resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-create-class-features-plugin@7.26.9':
    resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0
  '@babel/helper-member-expression-to-functions@7.25.9':
    resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-module-imports@7.25.9':
    resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-module-transforms@7.26.0':
    resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0
  '@babel/helper-optimise-call-expression@7.25.9':
    resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-plugin-utils@7.26.5':
    resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-replace-supers@7.26.5':
    resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0
  '@babel/helper-skip-transparent-expression-wrappers@7.25.9':
    resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-string-parser@7.25.9':
    resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-validator-identifier@7.25.9':
    resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
    engines: {node: '>=6.9.0'}
  '@babel/helper-validator-option@7.25.9':
    resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
    engines: {node: '>=6.9.0'}
  '@babel/helpers@7.26.9':
    resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==}
    engines: {node: '>=6.9.0'}
  '@babel/parser@7.26.9':
    resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==}
    engines: {node: '>=6.0.0'}
    hasBin: true
  '@babel/plugin-syntax-jsx@7.25.9':
    resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0-0
  '@babel/plugin-syntax-typescript@7.25.9':
    resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0-0
  '@babel/plugin-transform-typescript@7.26.8':
    resolution: {integrity: sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==}
    engines: {node: '>=6.9.0'}
    peerDependencies:
      '@babel/core': ^7.0.0-0
  '@babel/template@7.26.9':
    resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==}
    engines: {node: '>=6.9.0'}
  '@babel/traverse@7.26.9':
    resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==}
    engines: {node: '>=6.9.0'}
  '@babel/types@7.26.9':
    resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
    engines: {node: '>=6.9.0'}
  '@ctrl/tinycolor@3.6.1':
    resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
    engines: {node: '>=10'}
  '@element-plus/icons-vue@2.3.1':
    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
    peerDependencies:
      vue: ^3.2.0
  '@esbuild/aix-ppc64@0.24.2':
    resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [aix]
  '@esbuild/android-arm64@0.24.2':
    resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [android]
  '@esbuild/android-arm@0.24.2':
    resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [android]
  '@esbuild/android-x64@0.24.2':
    resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [android]
  '@esbuild/darwin-arm64@0.24.2':
    resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [darwin]
  '@esbuild/darwin-x64@0.24.2':
    resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [darwin]
  '@esbuild/freebsd-arm64@0.24.2':
    resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [freebsd]
  '@esbuild/freebsd-x64@0.24.2':
    resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [freebsd]
  '@esbuild/linux-arm64@0.24.2':
    resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [linux]
  '@esbuild/linux-arm@0.24.2':
    resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [linux]
  '@esbuild/linux-ia32@0.24.2':
    resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [linux]
  '@esbuild/linux-loong64@0.24.2':
    resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
    engines: {node: '>=18'}
    cpu: [loong64]
    os: [linux]
  '@esbuild/linux-mips64el@0.24.2':
    resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
    engines: {node: '>=18'}
    cpu: [mips64el]
    os: [linux]
  '@esbuild/linux-ppc64@0.24.2':
    resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [linux]
  '@esbuild/linux-riscv64@0.24.2':
    resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
    engines: {node: '>=18'}
    cpu: [riscv64]
    os: [linux]
  '@esbuild/linux-s390x@0.24.2':
    resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
    engines: {node: '>=18'}
    cpu: [s390x]
    os: [linux]
  '@esbuild/linux-x64@0.24.2':
    resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [linux]
  '@esbuild/netbsd-arm64@0.24.2':
    resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [netbsd]
  '@esbuild/netbsd-x64@0.24.2':
    resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [netbsd]
  '@esbuild/openbsd-arm64@0.24.2':
    resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openbsd]
  '@esbuild/openbsd-x64@0.24.2':
    resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [openbsd]
  '@esbuild/sunos-x64@0.24.2':
    resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [sunos]
  '@esbuild/win32-arm64@0.24.2':
    resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [win32]
  '@esbuild/win32-ia32@0.24.2':
    resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [win32]
  '@esbuild/win32-x64@0.24.2':
    resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [win32]
  '@floating-ui/core@1.6.9':
    resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
  '@floating-ui/dom@1.6.13':
    resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
  '@floating-ui/utils@0.2.9':
    resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
  '@jridgewell/gen-mapping@0.3.8':
    resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
    engines: {node: '>=6.0.0'}
  '@jridgewell/resolve-uri@3.1.2':
    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
    engines: {node: '>=6.0.0'}
  '@jridgewell/set-array@1.2.1':
    resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
    engines: {node: '>=6.0.0'}
  '@jridgewell/sourcemap-codec@1.5.0':
    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
  '@jridgewell/trace-mapping@0.3.25':
    resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
  '@microsoft/api-extractor-model@7.30.3':
    resolution: {integrity: sha512-yEAvq0F78MmStXdqz9TTT4PZ05Xu5R8nqgwI5xmUmQjWBQ9E6R2n8HB/iZMRciG4rf9iwI2mtuQwIzDXBvHn1w==}
  '@microsoft/api-extractor@7.50.0':
    resolution: {integrity: sha512-Ds/PHTiVzuENQsmXrJKkSdfgNkr/SDG/2rDef0AWl3BchAnXdO7gXaYsAkNx4gWiC4OngNA3fQfd3+BcQxP1DQ==}
    hasBin: true
  '@microsoft/tsdoc-config@0.17.1':
    resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==}
  '@microsoft/tsdoc@0.15.1':
    resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
  '@parcel/watcher-android-arm64@2.5.1':
    resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm64]
    os: [android]
  '@parcel/watcher-darwin-arm64@2.5.1':
    resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm64]
    os: [darwin]
  '@parcel/watcher-darwin-x64@2.5.1':
    resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
    engines: {node: '>= 10.0.0'}
    cpu: [x64]
    os: [darwin]
  '@parcel/watcher-freebsd-x64@2.5.1':
    resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
    engines: {node: '>= 10.0.0'}
    cpu: [x64]
    os: [freebsd]
  '@parcel/watcher-linux-arm-glibc@2.5.1':
    resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm]
    os: [linux]
    libc: [glibc]
  '@parcel/watcher-linux-arm-musl@2.5.1':
    resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm]
    os: [linux]
    libc: [musl]
  '@parcel/watcher-linux-arm64-glibc@2.5.1':
    resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm64]
    os: [linux]
    libc: [glibc]
  '@parcel/watcher-linux-arm64-musl@2.5.1':
    resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm64]
    os: [linux]
    libc: [musl]
  '@parcel/watcher-linux-x64-glibc@2.5.1':
    resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
    engines: {node: '>= 10.0.0'}
    cpu: [x64]
    os: [linux]
    libc: [glibc]
  '@parcel/watcher-linux-x64-musl@2.5.1':
    resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
    engines: {node: '>= 10.0.0'}
    cpu: [x64]
    os: [linux]
    libc: [musl]
  '@parcel/watcher-win32-arm64@2.5.1':
    resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
    engines: {node: '>= 10.0.0'}
    cpu: [arm64]
    os: [win32]
  '@parcel/watcher-win32-ia32@2.5.1':
    resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
    engines: {node: '>= 10.0.0'}
    cpu: [ia32]
    os: [win32]
  '@parcel/watcher-win32-x64@2.5.1':
    resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
    engines: {node: '>= 10.0.0'}
    cpu: [x64]
    os: [win32]
  '@parcel/watcher@2.5.1':
    resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
    engines: {node: '>= 10.0.0'}
  '@rollup/pluginutils@5.1.4':
    resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
    engines: {node: '>=14.0.0'}
    peerDependencies:
      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
    peerDependenciesMeta:
      rollup:
        optional: true
  '@rollup/rollup-android-arm-eabi@4.34.8':
    resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==}
    cpu: [arm]
    os: [android]
  '@rollup/rollup-android-arm64@4.34.8':
    resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==}
    cpu: [arm64]
    os: [android]
  '@rollup/rollup-darwin-arm64@4.34.8':
    resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==}
    cpu: [arm64]
    os: [darwin]
  '@rollup/rollup-darwin-x64@4.34.8':
    resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==}
    cpu: [x64]
    os: [darwin]
  '@rollup/rollup-freebsd-arm64@4.34.8':
    resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==}
    cpu: [arm64]
    os: [freebsd]
  '@rollup/rollup-freebsd-x64@4.34.8':
    resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==}
    cpu: [x64]
    os: [freebsd]
  '@rollup/rollup-linux-arm-gnueabihf@4.34.8':
    resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==}
    cpu: [arm]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-arm-musleabihf@4.34.8':
    resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==}
    cpu: [arm]
    os: [linux]
    libc: [musl]
  '@rollup/rollup-linux-arm64-gnu@4.34.8':
    resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==}
    cpu: [arm64]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-arm64-musl@4.34.8':
    resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==}
    cpu: [arm64]
    os: [linux]
    libc: [musl]
  '@rollup/rollup-linux-loongarch64-gnu@4.34.8':
    resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==}
    cpu: [loong64]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-powerpc64le-gnu@4.34.8':
    resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==}
    cpu: [ppc64]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-riscv64-gnu@4.34.8':
    resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==}
    cpu: [riscv64]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-s390x-gnu@4.34.8':
    resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==}
    cpu: [s390x]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-x64-gnu@4.34.8':
    resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==}
    cpu: [x64]
    os: [linux]
    libc: [glibc]
  '@rollup/rollup-linux-x64-musl@4.34.8':
    resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==}
    cpu: [x64]
    os: [linux]
    libc: [musl]
  '@rollup/rollup-win32-arm64-msvc@4.34.8':
    resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==}
    cpu: [arm64]
    os: [win32]
  '@rollup/rollup-win32-ia32-msvc@4.34.8':
    resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==}
    cpu: [ia32]
    os: [win32]
  '@rollup/rollup-win32-x64-msvc@4.34.8':
    resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==}
    cpu: [x64]
    os: [win32]
  '@rushstack/node-core-library@5.11.0':
    resolution: {integrity: sha512-I8+VzG9A0F3nH2rLpPd7hF8F7l5Xb7D+ldrWVZYegXM6CsKkvWc670RlgK3WX8/AseZfXA/vVrh0bpXe2Y2UDQ==}
    peerDependencies:
      '@types/node': '*'
    peerDependenciesMeta:
      '@types/node':
        optional: true
  '@rushstack/rig-package@0.5.3':
    resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==}
  '@rushstack/terminal@0.15.0':
    resolution: {integrity: sha512-vXQPRQ+vJJn4GVqxkwRe+UGgzNxdV8xuJZY2zem46Y0p3tlahucH9/hPmLGj2i9dQnUBFiRnoM9/KW7PYw8F4Q==}
    peerDependencies:
      '@types/node': '*'
    peerDependenciesMeta:
      '@types/node':
        optional: true
  '@rushstack/ts-command-line@4.23.5':
    resolution: {integrity: sha512-jg70HfoK44KfSP3MTiL5rxsZH7X1ktX3cZs9Sl8eDu1/LxJSbPsh0MOFRC710lIuYYSgxWjI5AjbCBAl7u3RxA==}
  '@sxzz/popperjs-es@2.11.7':
    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
  '@types/argparse@1.0.38':
    resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
  '@types/estree@1.0.6':
    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
  '@types/lodash-es@4.17.12':
    resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
  '@types/lodash@4.17.15':
    resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
  '@types/node@20.17.19':
    resolution: {integrity: sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==}
  '@types/web-bluetooth@0.0.16':
    resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
  '@vitejs/plugin-vue-jsx@4.1.1':
    resolution: {integrity: sha512-uMJqv/7u1zz/9NbWAD3XdjaY20tKTf17XVfQ9zq4wY1BjsB/PjpJPMe2xiG39QpP4ZdhYNhm4Hvo66uJrykNLA==}
    engines: {node: ^18.0.0 || >=20.0.0}
    peerDependencies:
      vite: ^5.0.0 || ^6.0.0
      vue: ^3.0.0
  '@vitejs/plugin-vue@5.2.1':
    resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
    engines: {node: ^18.0.0 || >=20.0.0}
    peerDependencies:
      vite: ^5.0.0 || ^6.0.0
      vue: ^3.2.25
  '@volar/language-core@2.4.11':
    resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==}
  '@volar/source-map@2.4.11':
    resolution: {integrity: sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==}
  '@volar/typescript@2.4.11':
    resolution: {integrity: sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==}
  '@vue/babel-helper-vue-transform-on@1.2.5':
    resolution: {integrity: sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==}
  '@vue/babel-plugin-jsx@1.2.5':
    resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==}
    peerDependencies:
      '@babel/core': ^7.0.0-0
    peerDependenciesMeta:
      '@babel/core':
        optional: true
  '@vue/babel-plugin-resolve-type@1.2.5':
    resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==}
    peerDependencies:
      '@babel/core': ^7.0.0-0
  '@vue/compiler-core@3.5.13':
    resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
  '@vue/compiler-dom@3.5.13':
    resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
  '@vue/compiler-sfc@3.5.13':
    resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
  '@vue/compiler-ssr@3.5.13':
    resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
  '@vue/compiler-vue2@2.7.16':
    resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
  '@vue/language-core@2.2.0':
    resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==}
    peerDependencies:
      typescript: '*'
    peerDependenciesMeta:
      typescript:
        optional: true
  '@vue/language-core@2.2.2':
    resolution: {integrity: sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==}
    peerDependencies:
      typescript: '*'
    peerDependenciesMeta:
      typescript:
        optional: true
  '@vue/reactivity@3.5.13':
    resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
  '@vue/runtime-core@3.5.13':
    resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
  '@vue/runtime-dom@3.5.13':
    resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
  '@vue/server-renderer@3.5.13':
    resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
    peerDependencies:
      vue: 3.5.13
  '@vue/shared@3.5.13':
    resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
  '@vueuse/core@9.13.0':
    resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
  '@vueuse/metadata@9.13.0':
    resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
  '@vueuse/shared@9.13.0':
    resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
  acorn@8.14.0:
    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
    engines: {node: '>=0.4.0'}
    hasBin: true
  ajv-draft-04@1.0.0:
    resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
    peerDependencies:
      ajv: ^8.5.0
    peerDependenciesMeta:
      ajv:
        optional: true
  ajv-formats@3.0.1:
    resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
    peerDependencies:
      ajv: ^8.0.0
    peerDependenciesMeta:
      ajv:
        optional: true
  ajv@8.12.0:
    resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
  ajv@8.13.0:
    resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==}
  alien-signals@0.4.14:
    resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
  alien-signals@1.0.3:
    resolution: {integrity: sha512-zQOh3wAYK5ujENxvBBR3CFGF/b6afaSzZ/c9yNhJ1ENrGHETvpUuKQsa93Qrclp0+PzTF93MaZ7scVp1uUozhA==}
  argparse@1.0.10:
    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
  async-validator@4.2.5:
    resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
  balanced-match@1.0.2:
    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
  brace-expansion@1.1.11:
    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
  brace-expansion@2.0.1:
    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
  braces@3.0.3:
    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
    engines: {node: '>=8'}
  browserslist@4.24.4:
    resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
    hasBin: true
  caniuse-lite@1.0.30001700:
    resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
  chokidar@4.0.3:
    resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
    engines: {node: '>= 14.16.0'}
  compare-versions@6.1.1:
    resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
  concat-map@0.0.1:
    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
  confbox@0.1.8:
    resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
  convert-source-map@2.0.0:
    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
  csstype@3.1.3:
    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
  dayjs@1.11.13:
    resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
  de-indent@1.0.2:
    resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
  debug@4.4.0:
    resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
    engines: {node: '>=6.0'}
    peerDependencies:
      supports-color: '*'
    peerDependenciesMeta:
      supports-color:
        optional: true
  detect-libc@1.0.3:
    resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
    engines: {node: '>=0.10'}
    hasBin: true
  electron-to-chromium@1.5.102:
    resolution: {integrity: sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==}
  element-plus@2.9.4:
    resolution: {integrity: sha512-sGnW0wd9zf6lEGixXV2gfwx3X6VTMkP52qTkX7zbURJ2oariyslrKTBh2txt1sdn1pUvj2l0KY3OfSXoZGmDOw==}
    peerDependencies:
      vue: ^3.2.0
  entities@4.5.0:
    resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
    engines: {node: '>=0.12'}
  esbuild@0.24.2:
    resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
    engines: {node: '>=18'}
    hasBin: true
  escalade@3.2.0:
    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
    engines: {node: '>=6'}
  escape-html@1.0.3:
    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
  estree-walker@2.0.2:
    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
  fast-deep-equal@3.1.3:
    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
  fill-range@7.1.1:
    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
    engines: {node: '>=8'}
  fs-extra@11.3.0:
    resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
    engines: {node: '>=14.14'}
  fsevents@2.3.3:
    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
    os: [darwin]
  function-bind@1.1.2:
    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
  gensync@1.0.0-beta.2:
    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
    engines: {node: '>=6.9.0'}
  globals@11.12.0:
    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
    engines: {node: '>=4'}
  graceful-fs@4.2.11:
    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
  has-flag@4.0.0:
    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
    engines: {node: '>=8'}
  hasown@2.0.2:
    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
    engines: {node: '>= 0.4'}
  he@1.2.0:
    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
    hasBin: true
  html-tags@3.3.1:
    resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
    engines: {node: '>=8'}
  immutable@5.0.3:
    resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
  import-lazy@4.0.0:
    resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
    engines: {node: '>=8'}
  is-core-module@2.16.1:
    resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
    engines: {node: '>= 0.4'}
  is-extglob@2.1.1:
    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
    engines: {node: '>=0.10.0'}
  is-glob@4.0.3:
    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
    engines: {node: '>=0.10.0'}
  is-number@7.0.0:
    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
    engines: {node: '>=0.12.0'}
  jju@1.4.0:
    resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
  js-tokens@4.0.0:
    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
  jsesc@3.1.0:
    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
    engines: {node: '>=6'}
    hasBin: true
  json-schema-traverse@1.0.0:
    resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
  json5@2.2.3:
    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
    engines: {node: '>=6'}
    hasBin: true
  jsonfile@6.1.0:
    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
  kolorist@1.8.0:
    resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
  local-pkg@0.5.1:
    resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
    engines: {node: '>=14'}
  lodash-es@4.17.21:
    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
  lodash-unified@1.0.3:
    resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
    peerDependencies:
      '@types/lodash-es': '*'
      lodash: '*'
      lodash-es: '*'
  lodash@4.17.21:
    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
  lru-cache@5.1.1:
    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
  lru-cache@6.0.0:
    resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
    engines: {node: '>=10'}
  magic-string@0.30.17:
    resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
  memoize-one@6.0.0:
    resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
  micromatch@4.0.8:
    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
    engines: {node: '>=8.6'}
  minimatch@3.0.8:
    resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
  minimatch@9.0.5:
    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
    engines: {node: '>=16 || 14 >=14.17'}
  mlly@1.7.4:
    resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
  ms@2.1.3:
    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
  muggle-string@0.4.1:
    resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
  nanoid@3.3.8:
    resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
    hasBin: true
  neo-async@2.6.2:
    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
  node-addon-api@7.1.1:
    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
  node-releases@2.0.19:
    resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
  normalize-wheel-es@1.2.0:
    resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
  path-browserify@1.0.1:
    resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
  path-parse@1.0.7:
    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
  pathe@2.0.3:
    resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
  picocolors@1.1.1:
    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
  picomatch@2.3.1:
    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
    engines: {node: '>=8.6'}
  picomatch@4.0.2:
    resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
    engines: {node: '>=12'}
  pkg-types@1.3.1:
    resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
  postcss@8.5.2:
    resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==}
    engines: {node: ^10 || ^12 || >=14}
  prettier@3.5.1:
    resolution: {integrity: sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==}
    engines: {node: '>=14'}
    hasBin: true
  punycode@2.3.1:
    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
    engines: {node: '>=6'}
  readdirp@4.1.2:
    resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
    engines: {node: '>= 14.18.0'}
  require-from-string@2.0.2:
    resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
    engines: {node: '>=0.10.0'}
  resolve@1.22.10:
    resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
    engines: {node: '>= 0.4'}
    hasBin: true
  rollup@4.34.8:
    resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==}
    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
    hasBin: true
  sass-loader@16.0.5:
    resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==}
    engines: {node: '>= 18.12.0'}
    peerDependencies:
      '@rspack/core': 0.x || 1.x
      node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
      sass: ^1.3.0
      sass-embedded: '*'
      webpack: ^5.0.0
    peerDependenciesMeta:
      '@rspack/core':
        optional: true
      node-sass:
        optional: true
      sass:
        optional: true
      sass-embedded:
        optional: true
      webpack:
        optional: true
  sass@1.85.0:
    resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==}
    engines: {node: '>=14.0.0'}
    hasBin: true
  semver@6.3.1:
    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
    hasBin: true
  semver@7.5.4:
    resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
    engines: {node: '>=10'}
    hasBin: true
  source-map-js@1.2.1:
    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
    engines: {node: '>=0.10.0'}
  source-map@0.6.1:
    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
    engines: {node: '>=0.10.0'}
  sprintf-js@1.0.3:
    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
  string-argv@0.3.2:
    resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
    engines: {node: '>=0.6.19'}
  strip-json-comments@3.1.1:
    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
    engines: {node: '>=8'}
  supports-color@8.1.1:
    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
    engines: {node: '>=10'}
  supports-preserve-symlinks-flag@1.0.0:
    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
    engines: {node: '>= 0.4'}
  svg-tags@1.0.0:
    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
  to-regex-range@5.0.1:
    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
    engines: {node: '>=8.0'}
  typescript@5.7.2:
    resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
    engines: {node: '>=14.17'}
    hasBin: true
  typescript@5.7.3:
    resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
    engines: {node: '>=14.17'}
    hasBin: true
  ufo@1.5.4:
    resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
  undici-types@6.19.8:
    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
  universalify@2.0.1:
    resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
    engines: {node: '>= 10.0.0'}
  update-browserslist-db@1.1.2:
    resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==}
    hasBin: true
    peerDependencies:
      browserslist: '>= 4.21.0'
  uri-js@4.4.1:
    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
  vite-plugin-dts@4.5.0:
    resolution: {integrity: sha512-M1lrPTdi7gilLYRZoLmGYnl4fbPryVYsehPN9JgaxjJKTs8/f7tuAlvCCvOLB5gRDQTTKnptBcB0ACsaw2wNLw==}
    peerDependencies:
      typescript: '*'
      vite: '*'
    peerDependenciesMeta:
      vite:
        optional: true
  vite@6.1.0:
    resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==}
    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
    hasBin: true
    peerDependencies:
      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
      jiti: '>=1.21.0'
      less: '*'
      lightningcss: ^1.21.0
      sass: '*'
      sass-embedded: '*'
      stylus: '*'
      sugarss: '*'
      terser: ^5.16.0
      tsx: ^4.8.1
      yaml: ^2.4.2
    peerDependenciesMeta:
      '@types/node':
        optional: true
      jiti:
        optional: true
      less:
        optional: true
      lightningcss:
        optional: true
      sass:
        optional: true
      sass-embedded:
        optional: true
      stylus:
        optional: true
      sugarss:
        optional: true
      terser:
        optional: true
      tsx:
        optional: true
      yaml:
        optional: true
  vscode-uri@3.1.0:
    resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
  vue-demi@0.14.10:
    resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
    engines: {node: '>=12'}
    hasBin: true
    peerDependencies:
      '@vue/composition-api': ^1.0.0-rc.1
      vue: ^3.0.0-0 || ^2.6.0
    peerDependenciesMeta:
      '@vue/composition-api':
        optional: true
  vue-tsc@2.2.2:
    resolution: {integrity: sha512-1icPKkxAA5KTAaSwg0wVWdE48EdsH8fgvcbAiqojP4jXKl6LEM3soiW1aG/zrWrFt8Mw1ncG2vG1PvpZpVfehA==}
    hasBin: true
    peerDependencies:
      typescript: '>=5.0.0'
  vue@3.5.13:
    resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
    peerDependencies:
      typescript: '*'
    peerDependenciesMeta:
      typescript:
        optional: true
  yallist@3.1.1:
    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
  yallist@4.0.0:
    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
snapshots:
  '@ampproject/remapping@2.3.0':
    dependencies:
      '@jridgewell/gen-mapping': 0.3.8
      '@jridgewell/trace-mapping': 0.3.25
  '@babel/code-frame@7.26.2':
    dependencies:
      '@babel/helper-validator-identifier': 7.25.9
      js-tokens: 4.0.0
      picocolors: 1.1.1
  '@babel/compat-data@7.26.8': {}
  '@babel/core@7.26.9':
    dependencies:
      '@ampproject/remapping': 2.3.0
      '@babel/code-frame': 7.26.2
      '@babel/generator': 7.26.9
      '@babel/helper-compilation-targets': 7.26.5
      '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9)
      '@babel/helpers': 7.26.9
      '@babel/parser': 7.26.9
      '@babel/template': 7.26.9
      '@babel/traverse': 7.26.9
      '@babel/types': 7.26.9
      convert-source-map: 2.0.0
      debug: 4.4.0
      gensync: 1.0.0-beta.2
      json5: 2.2.3
      semver: 6.3.1
    transitivePeerDependencies:
      - supports-color
  '@babel/generator@7.26.9':
    dependencies:
      '@babel/parser': 7.26.9
      '@babel/types': 7.26.9
      '@jridgewell/gen-mapping': 0.3.8
      '@jridgewell/trace-mapping': 0.3.25
      jsesc: 3.1.0
  '@babel/helper-annotate-as-pure@7.25.9':
    dependencies:
      '@babel/types': 7.26.9
  '@babel/helper-compilation-targets@7.26.5':
    dependencies:
      '@babel/compat-data': 7.26.8
      '@babel/helper-validator-option': 7.25.9
      browserslist: 4.24.4
      lru-cache: 5.1.1
      semver: 6.3.1
  '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-annotate-as-pure': 7.25.9
      '@babel/helper-member-expression-to-functions': 7.25.9
      '@babel/helper-optimise-call-expression': 7.25.9
      '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.9)
      '@babel/helper-skip-transparent-expression-wrappers': 7.25.9
      '@babel/traverse': 7.26.9
      semver: 6.3.1
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-member-expression-to-functions@7.25.9':
    dependencies:
      '@babel/traverse': 7.26.9
      '@babel/types': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-module-imports@7.25.9':
    dependencies:
      '@babel/traverse': 7.26.9
      '@babel/types': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-module-imports': 7.25.9
      '@babel/helper-validator-identifier': 7.25.9
      '@babel/traverse': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-optimise-call-expression@7.25.9':
    dependencies:
      '@babel/types': 7.26.9
  '@babel/helper-plugin-utils@7.26.5': {}
  '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-member-expression-to-functions': 7.25.9
      '@babel/helper-optimise-call-expression': 7.25.9
      '@babel/traverse': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-skip-transparent-expression-wrappers@7.25.9':
    dependencies:
      '@babel/traverse': 7.26.9
      '@babel/types': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@babel/helper-string-parser@7.25.9': {}
  '@babel/helper-validator-identifier@7.25.9': {}
  '@babel/helper-validator-option@7.25.9': {}
  '@babel/helpers@7.26.9':
    dependencies:
      '@babel/template': 7.26.9
      '@babel/types': 7.26.9
  '@babel/parser@7.26.9':
    dependencies:
      '@babel/types': 7.26.9
  '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-plugin-utils': 7.26.5
  '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-plugin-utils': 7.26.5
  '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.26.9)':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/helper-annotate-as-pure': 7.25.9
      '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9)
      '@babel/helper-plugin-utils': 7.26.5
      '@babel/helper-skip-transparent-expression-wrappers': 7.25.9
      '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.9)
    transitivePeerDependencies:
      - supports-color
  '@babel/template@7.26.9':
    dependencies:
      '@babel/code-frame': 7.26.2
      '@babel/parser': 7.26.9
      '@babel/types': 7.26.9
  '@babel/traverse@7.26.9':
    dependencies:
      '@babel/code-frame': 7.26.2
      '@babel/generator': 7.26.9
      '@babel/parser': 7.26.9
      '@babel/template': 7.26.9
      '@babel/types': 7.26.9
      debug: 4.4.0
      globals: 11.12.0
    transitivePeerDependencies:
      - supports-color
  '@babel/types@7.26.9':
    dependencies:
      '@babel/helper-string-parser': 7.25.9
      '@babel/helper-validator-identifier': 7.25.9
  '@ctrl/tinycolor@3.6.1': {}
  '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      vue: 3.5.13(typescript@5.7.3)
  '@esbuild/aix-ppc64@0.24.2':
    optional: true
  '@esbuild/android-arm64@0.24.2':
    optional: true
  '@esbuild/android-arm@0.24.2':
    optional: true
  '@esbuild/android-x64@0.24.2':
    optional: true
  '@esbuild/darwin-arm64@0.24.2':
    optional: true
  '@esbuild/darwin-x64@0.24.2':
    optional: true
  '@esbuild/freebsd-arm64@0.24.2':
    optional: true
  '@esbuild/freebsd-x64@0.24.2':
    optional: true
  '@esbuild/linux-arm64@0.24.2':
    optional: true
  '@esbuild/linux-arm@0.24.2':
    optional: true
  '@esbuild/linux-ia32@0.24.2':
    optional: true
  '@esbuild/linux-loong64@0.24.2':
    optional: true
  '@esbuild/linux-mips64el@0.24.2':
    optional: true
  '@esbuild/linux-ppc64@0.24.2':
    optional: true
  '@esbuild/linux-riscv64@0.24.2':
    optional: true
  '@esbuild/linux-s390x@0.24.2':
    optional: true
  '@esbuild/linux-x64@0.24.2':
    optional: true
  '@esbuild/netbsd-arm64@0.24.2':
    optional: true
  '@esbuild/netbsd-x64@0.24.2':
    optional: true
  '@esbuild/openbsd-arm64@0.24.2':
    optional: true
  '@esbuild/openbsd-x64@0.24.2':
    optional: true
  '@esbuild/sunos-x64@0.24.2':
    optional: true
  '@esbuild/win32-arm64@0.24.2':
    optional: true
  '@esbuild/win32-ia32@0.24.2':
    optional: true
  '@esbuild/win32-x64@0.24.2':
    optional: true
  '@floating-ui/core@1.6.9':
    dependencies:
      '@floating-ui/utils': 0.2.9
  '@floating-ui/dom@1.6.13':
    dependencies:
      '@floating-ui/core': 1.6.9
      '@floating-ui/utils': 0.2.9
  '@floating-ui/utils@0.2.9': {}
  '@jridgewell/gen-mapping@0.3.8':
    dependencies:
      '@jridgewell/set-array': 1.2.1
      '@jridgewell/sourcemap-codec': 1.5.0
      '@jridgewell/trace-mapping': 0.3.25
  '@jridgewell/resolve-uri@3.1.2': {}
  '@jridgewell/set-array@1.2.1': {}
  '@jridgewell/sourcemap-codec@1.5.0': {}
  '@jridgewell/trace-mapping@0.3.25':
    dependencies:
      '@jridgewell/resolve-uri': 3.1.2
      '@jridgewell/sourcemap-codec': 1.5.0
  '@microsoft/api-extractor-model@7.30.3(@types/node@20.17.19)':
    dependencies:
      '@microsoft/tsdoc': 0.15.1
      '@microsoft/tsdoc-config': 0.17.1
      '@rushstack/node-core-library': 5.11.0(@types/node@20.17.19)
    transitivePeerDependencies:
      - '@types/node'
  '@microsoft/api-extractor@7.50.0(@types/node@20.17.19)':
    dependencies:
      '@microsoft/api-extractor-model': 7.30.3(@types/node@20.17.19)
      '@microsoft/tsdoc': 0.15.1
      '@microsoft/tsdoc-config': 0.17.1
      '@rushstack/node-core-library': 5.11.0(@types/node@20.17.19)
      '@rushstack/rig-package': 0.5.3
      '@rushstack/terminal': 0.15.0(@types/node@20.17.19)
      '@rushstack/ts-command-line': 4.23.5(@types/node@20.17.19)
      lodash: 4.17.21
      minimatch: 3.0.8
      resolve: 1.22.10
      semver: 7.5.4
      source-map: 0.6.1
      typescript: 5.7.2
    transitivePeerDependencies:
      - '@types/node'
  '@microsoft/tsdoc-config@0.17.1':
    dependencies:
      '@microsoft/tsdoc': 0.15.1
      ajv: 8.12.0
      jju: 1.4.0
      resolve: 1.22.10
  '@microsoft/tsdoc@0.15.1': {}
  '@parcel/watcher-android-arm64@2.5.1':
    optional: true
  '@parcel/watcher-darwin-arm64@2.5.1':
    optional: true
  '@parcel/watcher-darwin-x64@2.5.1':
    optional: true
  '@parcel/watcher-freebsd-x64@2.5.1':
    optional: true
  '@parcel/watcher-linux-arm-glibc@2.5.1':
    optional: true
  '@parcel/watcher-linux-arm-musl@2.5.1':
    optional: true
  '@parcel/watcher-linux-arm64-glibc@2.5.1':
    optional: true
  '@parcel/watcher-linux-arm64-musl@2.5.1':
    optional: true
  '@parcel/watcher-linux-x64-glibc@2.5.1':
    optional: true
  '@parcel/watcher-linux-x64-musl@2.5.1':
    optional: true
  '@parcel/watcher-win32-arm64@2.5.1':
    optional: true
  '@parcel/watcher-win32-ia32@2.5.1':
    optional: true
  '@parcel/watcher-win32-x64@2.5.1':
    optional: true
  '@parcel/watcher@2.5.1':
    dependencies:
      detect-libc: 1.0.3
      is-glob: 4.0.3
      micromatch: 4.0.8
      node-addon-api: 7.1.1
    optionalDependencies:
      '@parcel/watcher-android-arm64': 2.5.1
      '@parcel/watcher-darwin-arm64': 2.5.1
      '@parcel/watcher-darwin-x64': 2.5.1
      '@parcel/watcher-freebsd-x64': 2.5.1
      '@parcel/watcher-linux-arm-glibc': 2.5.1
      '@parcel/watcher-linux-arm-musl': 2.5.1
      '@parcel/watcher-linux-arm64-glibc': 2.5.1
      '@parcel/watcher-linux-arm64-musl': 2.5.1
      '@parcel/watcher-linux-x64-glibc': 2.5.1
      '@parcel/watcher-linux-x64-musl': 2.5.1
      '@parcel/watcher-win32-arm64': 2.5.1
      '@parcel/watcher-win32-ia32': 2.5.1
      '@parcel/watcher-win32-x64': 2.5.1
    optional: true
  '@rollup/pluginutils@5.1.4(rollup@4.34.8)':
    dependencies:
      '@types/estree': 1.0.6
      estree-walker: 2.0.2
      picomatch: 4.0.2
    optionalDependencies:
      rollup: 4.34.8
  '@rollup/rollup-android-arm-eabi@4.34.8':
    optional: true
  '@rollup/rollup-android-arm64@4.34.8':
    optional: true
  '@rollup/rollup-darwin-arm64@4.34.8':
    optional: true
  '@rollup/rollup-darwin-x64@4.34.8':
    optional: true
  '@rollup/rollup-freebsd-arm64@4.34.8':
    optional: true
  '@rollup/rollup-freebsd-x64@4.34.8':
    optional: true
  '@rollup/rollup-linux-arm-gnueabihf@4.34.8':
    optional: true
  '@rollup/rollup-linux-arm-musleabihf@4.34.8':
    optional: true
  '@rollup/rollup-linux-arm64-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-arm64-musl@4.34.8':
    optional: true
  '@rollup/rollup-linux-loongarch64-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-powerpc64le-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-riscv64-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-s390x-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-x64-gnu@4.34.8':
    optional: true
  '@rollup/rollup-linux-x64-musl@4.34.8':
    optional: true
  '@rollup/rollup-win32-arm64-msvc@4.34.8':
    optional: true
  '@rollup/rollup-win32-ia32-msvc@4.34.8':
    optional: true
  '@rollup/rollup-win32-x64-msvc@4.34.8':
    optional: true
  '@rushstack/node-core-library@5.11.0(@types/node@20.17.19)':
    dependencies:
      ajv: 8.13.0
      ajv-draft-04: 1.0.0(ajv@8.13.0)
      ajv-formats: 3.0.1(ajv@8.13.0)
      fs-extra: 11.3.0
      import-lazy: 4.0.0
      jju: 1.4.0
      resolve: 1.22.10
      semver: 7.5.4
    optionalDependencies:
      '@types/node': 20.17.19
  '@rushstack/rig-package@0.5.3':
    dependencies:
      resolve: 1.22.10
      strip-json-comments: 3.1.1
  '@rushstack/terminal@0.15.0(@types/node@20.17.19)':
    dependencies:
      '@rushstack/node-core-library': 5.11.0(@types/node@20.17.19)
      supports-color: 8.1.1
    optionalDependencies:
      '@types/node': 20.17.19
  '@rushstack/ts-command-line@4.23.5(@types/node@20.17.19)':
    dependencies:
      '@rushstack/terminal': 0.15.0(@types/node@20.17.19)
      '@types/argparse': 1.0.38
      argparse: 1.0.10
      string-argv: 0.3.2
    transitivePeerDependencies:
      - '@types/node'
  '@sxzz/popperjs-es@2.11.7': {}
  '@types/argparse@1.0.38': {}
  '@types/estree@1.0.6': {}
  '@types/lodash-es@4.17.12':
    dependencies:
      '@types/lodash': 4.17.15
  '@types/lodash@4.17.15': {}
  '@types/node@20.17.19':
    dependencies:
      undici-types: 6.19.8
  '@types/web-bluetooth@0.0.16': {}
  '@vitejs/plugin-vue-jsx@4.1.1(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      '@babel/core': 7.26.9
      '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9)
      '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.9)
      vite: 6.1.0(@types/node@20.17.19)(sass@1.85.0)
      vue: 3.5.13(typescript@5.7.3)
    transitivePeerDependencies:
      - supports-color
  '@vitejs/plugin-vue@5.2.1(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0))(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      vite: 6.1.0(@types/node@20.17.19)(sass@1.85.0)
      vue: 3.5.13(typescript@5.7.3)
  '@volar/language-core@2.4.11':
    dependencies:
      '@volar/source-map': 2.4.11
  '@volar/source-map@2.4.11': {}
  '@volar/typescript@2.4.11':
    dependencies:
      '@volar/language-core': 2.4.11
      path-browserify: 1.0.1
      vscode-uri: 3.1.0
  '@vue/babel-helper-vue-transform-on@1.2.5': {}
  '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.26.9)':
    dependencies:
      '@babel/helper-module-imports': 7.25.9
      '@babel/helper-plugin-utils': 7.26.5
      '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9)
      '@babel/template': 7.26.9
      '@babel/traverse': 7.26.9
      '@babel/types': 7.26.9
      '@vue/babel-helper-vue-transform-on': 1.2.5
      '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.26.9)
      html-tags: 3.3.1
      svg-tags: 1.0.0
    optionalDependencies:
      '@babel/core': 7.26.9
    transitivePeerDependencies:
      - supports-color
  '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.26.9)':
    dependencies:
      '@babel/code-frame': 7.26.2
      '@babel/core': 7.26.9
      '@babel/helper-module-imports': 7.25.9
      '@babel/helper-plugin-utils': 7.26.5
      '@babel/parser': 7.26.9
      '@vue/compiler-sfc': 3.5.13
    transitivePeerDependencies:
      - supports-color
  '@vue/compiler-core@3.5.13':
    dependencies:
      '@babel/parser': 7.26.9
      '@vue/shared': 3.5.13
      entities: 4.5.0
      estree-walker: 2.0.2
      source-map-js: 1.2.1
  '@vue/compiler-dom@3.5.13':
    dependencies:
      '@vue/compiler-core': 3.5.13
      '@vue/shared': 3.5.13
  '@vue/compiler-sfc@3.5.13':
    dependencies:
      '@babel/parser': 7.26.9
      '@vue/compiler-core': 3.5.13
      '@vue/compiler-dom': 3.5.13
      '@vue/compiler-ssr': 3.5.13
      '@vue/shared': 3.5.13
      estree-walker: 2.0.2
      magic-string: 0.30.17
      postcss: 8.5.2
      source-map-js: 1.2.1
  '@vue/compiler-ssr@3.5.13':
    dependencies:
      '@vue/compiler-dom': 3.5.13
      '@vue/shared': 3.5.13
  '@vue/compiler-vue2@2.7.16':
    dependencies:
      de-indent: 1.0.2
      he: 1.2.0
  '@vue/language-core@2.2.0(typescript@5.7.3)':
    dependencies:
      '@volar/language-core': 2.4.11
      '@vue/compiler-dom': 3.5.13
      '@vue/compiler-vue2': 2.7.16
      '@vue/shared': 3.5.13
      alien-signals: 0.4.14
      minimatch: 9.0.5
      muggle-string: 0.4.1
      path-browserify: 1.0.1
    optionalDependencies:
      typescript: 5.7.3
  '@vue/language-core@2.2.2(typescript@5.7.3)':
    dependencies:
      '@volar/language-core': 2.4.11
      '@vue/compiler-dom': 3.5.13
      '@vue/compiler-vue2': 2.7.16
      '@vue/shared': 3.5.13
      alien-signals: 1.0.3
      minimatch: 9.0.5
      muggle-string: 0.4.1
      path-browserify: 1.0.1
    optionalDependencies:
      typescript: 5.7.3
  '@vue/reactivity@3.5.13':
    dependencies:
      '@vue/shared': 3.5.13
  '@vue/runtime-core@3.5.13':
    dependencies:
      '@vue/reactivity': 3.5.13
      '@vue/shared': 3.5.13
  '@vue/runtime-dom@3.5.13':
    dependencies:
      '@vue/reactivity': 3.5.13
      '@vue/runtime-core': 3.5.13
      '@vue/shared': 3.5.13
      csstype: 3.1.3
  '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      '@vue/compiler-ssr': 3.5.13
      '@vue/shared': 3.5.13
      vue: 3.5.13(typescript@5.7.3)
  '@vue/shared@3.5.13': {}
  '@vueuse/core@9.13.0(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      '@types/web-bluetooth': 0.0.16
      '@vueuse/metadata': 9.13.0
      '@vueuse/shared': 9.13.0(vue@3.5.13(typescript@5.7.3))
      vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
    transitivePeerDependencies:
      - '@vue/composition-api'
      - vue
  '@vueuse/metadata@9.13.0': {}
  '@vueuse/shared@9.13.0(vue@3.5.13(typescript@5.7.3))':
    dependencies:
      vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3))
    transitivePeerDependencies:
      - '@vue/composition-api'
      - vue
  acorn@8.14.0: {}
  ajv-draft-04@1.0.0(ajv@8.13.0):
    optionalDependencies:
      ajv: 8.13.0
  ajv-formats@3.0.1(ajv@8.13.0):
    optionalDependencies:
      ajv: 8.13.0
  ajv@8.12.0:
    dependencies:
      fast-deep-equal: 3.1.3
      json-schema-traverse: 1.0.0
      require-from-string: 2.0.2
      uri-js: 4.4.1
  ajv@8.13.0:
    dependencies:
      fast-deep-equal: 3.1.3
      json-schema-traverse: 1.0.0
      require-from-string: 2.0.2
      uri-js: 4.4.1
  alien-signals@0.4.14: {}
  alien-signals@1.0.3: {}
  argparse@1.0.10:
    dependencies:
      sprintf-js: 1.0.3
  async-validator@4.2.5: {}
  balanced-match@1.0.2: {}
  brace-expansion@1.1.11:
    dependencies:
      balanced-match: 1.0.2
      concat-map: 0.0.1
  brace-expansion@2.0.1:
    dependencies:
      balanced-match: 1.0.2
  braces@3.0.3:
    dependencies:
      fill-range: 7.1.1
    optional: true
  browserslist@4.24.4:
    dependencies:
      caniuse-lite: 1.0.30001700
      electron-to-chromium: 1.5.102
      node-releases: 2.0.19
      update-browserslist-db: 1.1.2(browserslist@4.24.4)
  caniuse-lite@1.0.30001700: {}
  chokidar@4.0.3:
    dependencies:
      readdirp: 4.1.2
  compare-versions@6.1.1: {}
  concat-map@0.0.1: {}
  confbox@0.1.8: {}
  convert-source-map@2.0.0: {}
  csstype@3.1.3: {}
  dayjs@1.11.13: {}
  de-indent@1.0.2: {}
  debug@4.4.0:
    dependencies:
      ms: 2.1.3
  detect-libc@1.0.3:
    optional: true
  electron-to-chromium@1.5.102: {}
  element-plus@2.9.4(vue@3.5.13(typescript@5.7.3)):
    dependencies:
      '@ctrl/tinycolor': 3.6.1
      '@element-plus/icons-vue': 2.3.1(vue@3.5.13(typescript@5.7.3))
      '@floating-ui/dom': 1.6.13
      '@popperjs/core': '@sxzz/popperjs-es@2.11.7'
      '@types/lodash': 4.17.15
      '@types/lodash-es': 4.17.12
      '@vueuse/core': 9.13.0(vue@3.5.13(typescript@5.7.3))
      async-validator: 4.2.5
      dayjs: 1.11.13
      escape-html: 1.0.3
      lodash: 4.17.21
      lodash-es: 4.17.21
      lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
      memoize-one: 6.0.0
      normalize-wheel-es: 1.2.0
      vue: 3.5.13(typescript@5.7.3)
    transitivePeerDependencies:
      - '@vue/composition-api'
  entities@4.5.0: {}
  esbuild@0.24.2:
    optionalDependencies:
      '@esbuild/aix-ppc64': 0.24.2
      '@esbuild/android-arm': 0.24.2
      '@esbuild/android-arm64': 0.24.2
      '@esbuild/android-x64': 0.24.2
      '@esbuild/darwin-arm64': 0.24.2
      '@esbuild/darwin-x64': 0.24.2
      '@esbuild/freebsd-arm64': 0.24.2
      '@esbuild/freebsd-x64': 0.24.2
      '@esbuild/linux-arm': 0.24.2
      '@esbuild/linux-arm64': 0.24.2
      '@esbuild/linux-ia32': 0.24.2
      '@esbuild/linux-loong64': 0.24.2
      '@esbuild/linux-mips64el': 0.24.2
      '@esbuild/linux-ppc64': 0.24.2
      '@esbuild/linux-riscv64': 0.24.2
      '@esbuild/linux-s390x': 0.24.2
      '@esbuild/linux-x64': 0.24.2
      '@esbuild/netbsd-arm64': 0.24.2
      '@esbuild/netbsd-x64': 0.24.2
      '@esbuild/openbsd-arm64': 0.24.2
      '@esbuild/openbsd-x64': 0.24.2
      '@esbuild/sunos-x64': 0.24.2
      '@esbuild/win32-arm64': 0.24.2
      '@esbuild/win32-ia32': 0.24.2
      '@esbuild/win32-x64': 0.24.2
  escalade@3.2.0: {}
  escape-html@1.0.3: {}
  estree-walker@2.0.2: {}
  fast-deep-equal@3.1.3: {}
  fill-range@7.1.1:
    dependencies:
      to-regex-range: 5.0.1
    optional: true
  fs-extra@11.3.0:
    dependencies:
      graceful-fs: 4.2.11
      jsonfile: 6.1.0
      universalify: 2.0.1
  fsevents@2.3.3:
    optional: true
  function-bind@1.1.2: {}
  gensync@1.0.0-beta.2: {}
  globals@11.12.0: {}
  graceful-fs@4.2.11: {}
  has-flag@4.0.0: {}
  hasown@2.0.2:
    dependencies:
      function-bind: 1.1.2
  he@1.2.0: {}
  html-tags@3.3.1: {}
  immutable@5.0.3: {}
  import-lazy@4.0.0: {}
  is-core-module@2.16.1:
    dependencies:
      hasown: 2.0.2
  is-extglob@2.1.1:
    optional: true
  is-glob@4.0.3:
    dependencies:
      is-extglob: 2.1.1
    optional: true
  is-number@7.0.0:
    optional: true
  jju@1.4.0: {}
  js-tokens@4.0.0: {}
  jsesc@3.1.0: {}
  json-schema-traverse@1.0.0: {}
  json5@2.2.3: {}
  jsonfile@6.1.0:
    dependencies:
      universalify: 2.0.1
    optionalDependencies:
      graceful-fs: 4.2.11
  kolorist@1.8.0: {}
  local-pkg@0.5.1:
    dependencies:
      mlly: 1.7.4
      pkg-types: 1.3.1
  lodash-es@4.17.21: {}
  lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
    dependencies:
      '@types/lodash-es': 4.17.12
      lodash: 4.17.21
      lodash-es: 4.17.21
  lodash@4.17.21: {}
  lru-cache@5.1.1:
    dependencies:
      yallist: 3.1.1
  lru-cache@6.0.0:
    dependencies:
      yallist: 4.0.0
  magic-string@0.30.17:
    dependencies:
      '@jridgewell/sourcemap-codec': 1.5.0
  memoize-one@6.0.0: {}
  micromatch@4.0.8:
    dependencies:
      braces: 3.0.3
      picomatch: 2.3.1
    optional: true
  minimatch@3.0.8:
    dependencies:
      brace-expansion: 1.1.11
  minimatch@9.0.5:
    dependencies:
      brace-expansion: 2.0.1
  mlly@1.7.4:
    dependencies:
      acorn: 8.14.0
      pathe: 2.0.3
      pkg-types: 1.3.1
      ufo: 1.5.4
  ms@2.1.3: {}
  muggle-string@0.4.1: {}
  nanoid@3.3.8: {}
  neo-async@2.6.2: {}
  node-addon-api@7.1.1:
    optional: true
  node-releases@2.0.19: {}
  normalize-wheel-es@1.2.0: {}
  path-browserify@1.0.1: {}
  path-parse@1.0.7: {}
  pathe@2.0.3: {}
  picocolors@1.1.1: {}
  picomatch@2.3.1:
    optional: true
  picomatch@4.0.2: {}
  pkg-types@1.3.1:
    dependencies:
      confbox: 0.1.8
      mlly: 1.7.4
      pathe: 2.0.3
  postcss@8.5.2:
    dependencies:
      nanoid: 3.3.8
      picocolors: 1.1.1
      source-map-js: 1.2.1
  prettier@3.5.1: {}
  punycode@2.3.1: {}
  readdirp@4.1.2: {}
  require-from-string@2.0.2: {}
  resolve@1.22.10:
    dependencies:
      is-core-module: 2.16.1
      path-parse: 1.0.7
      supports-preserve-symlinks-flag: 1.0.0
  rollup@4.34.8:
    dependencies:
      '@types/estree': 1.0.6
    optionalDependencies:
      '@rollup/rollup-android-arm-eabi': 4.34.8
      '@rollup/rollup-android-arm64': 4.34.8
      '@rollup/rollup-darwin-arm64': 4.34.8
      '@rollup/rollup-darwin-x64': 4.34.8
      '@rollup/rollup-freebsd-arm64': 4.34.8
      '@rollup/rollup-freebsd-x64': 4.34.8
      '@rollup/rollup-linux-arm-gnueabihf': 4.34.8
      '@rollup/rollup-linux-arm-musleabihf': 4.34.8
      '@rollup/rollup-linux-arm64-gnu': 4.34.8
      '@rollup/rollup-linux-arm64-musl': 4.34.8
      '@rollup/rollup-linux-loongarch64-gnu': 4.34.8
      '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8
      '@rollup/rollup-linux-riscv64-gnu': 4.34.8
      '@rollup/rollup-linux-s390x-gnu': 4.34.8
      '@rollup/rollup-linux-x64-gnu': 4.34.8
      '@rollup/rollup-linux-x64-musl': 4.34.8
      '@rollup/rollup-win32-arm64-msvc': 4.34.8
      '@rollup/rollup-win32-ia32-msvc': 4.34.8
      '@rollup/rollup-win32-x64-msvc': 4.34.8
      fsevents: 2.3.3
  sass-loader@16.0.5(sass@1.85.0):
    dependencies:
      neo-async: 2.6.2
    optionalDependencies:
      sass: 1.85.0
  sass@1.85.0:
    dependencies:
      chokidar: 4.0.3
      immutable: 5.0.3
      source-map-js: 1.2.1
    optionalDependencies:
      '@parcel/watcher': 2.5.1
  semver@6.3.1: {}
  semver@7.5.4:
    dependencies:
      lru-cache: 6.0.0
  source-map-js@1.2.1: {}
  source-map@0.6.1: {}
  sprintf-js@1.0.3: {}
  string-argv@0.3.2: {}
  strip-json-comments@3.1.1: {}
  supports-color@8.1.1:
    dependencies:
      has-flag: 4.0.0
  supports-preserve-symlinks-flag@1.0.0: {}
  svg-tags@1.0.0: {}
  to-regex-range@5.0.1:
    dependencies:
      is-number: 7.0.0
    optional: true
  typescript@5.7.2: {}
  typescript@5.7.3: {}
  ufo@1.5.4: {}
  undici-types@6.19.8: {}
  universalify@2.0.1: {}
  update-browserslist-db@1.1.2(browserslist@4.24.4):
    dependencies:
      browserslist: 4.24.4
      escalade: 3.2.0
      picocolors: 1.1.1
  uri-js@4.4.1:
    dependencies:
      punycode: 2.3.1
  vite-plugin-dts@4.5.0(@types/node@20.17.19)(rollup@4.34.8)(typescript@5.7.3)(vite@6.1.0(@types/node@20.17.19)(sass@1.85.0)):
    dependencies:
      '@microsoft/api-extractor': 7.50.0(@types/node@20.17.19)
      '@rollup/pluginutils': 5.1.4(rollup@4.34.8)
      '@volar/typescript': 2.4.11
      '@vue/language-core': 2.2.0(typescript@5.7.3)
      compare-versions: 6.1.1
      debug: 4.4.0
      kolorist: 1.8.0
      local-pkg: 0.5.1
      magic-string: 0.30.17
      typescript: 5.7.3
    optionalDependencies:
      vite: 6.1.0(@types/node@20.17.19)(sass@1.85.0)
    transitivePeerDependencies:
      - '@types/node'
      - rollup
      - supports-color
  vite@6.1.0(@types/node@20.17.19)(sass@1.85.0):
    dependencies:
      esbuild: 0.24.2
      postcss: 8.5.2
      rollup: 4.34.8
    optionalDependencies:
      '@types/node': 20.17.19
      fsevents: 2.3.3
      sass: 1.85.0
  vscode-uri@3.1.0: {}
  vue-demi@0.14.10(vue@3.5.13(typescript@5.7.3)):
    dependencies:
      vue: 3.5.13(typescript@5.7.3)
  vue-tsc@2.2.2(typescript@5.7.3):
    dependencies:
      '@volar/typescript': 2.4.11
      '@vue/language-core': 2.2.2(typescript@5.7.3)
      typescript: 5.7.3
  vue@3.5.13(typescript@5.7.3):
    dependencies:
      '@vue/compiler-dom': 3.5.13
      '@vue/compiler-sfc': 3.5.13
      '@vue/runtime-dom': 3.5.13
      '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.3))
      '@vue/shared': 3.5.13
    optionalDependencies:
      typescript: 5.7.3
  yallist@3.1.1: {}
  yallist@4.0.0: {}
packages/crud/src/App.vue
New file
@@ -0,0 +1,208 @@
<template>
    <div>
        <div class="title">CRUD DEMO v8</div>
        <cl-crud ref="Crud">
            <div class="search">
                <cl-search ref="Search" />
            </div>
            <cl-row>
                <cl-add-btn />
                <cl-flex1 />
                <cl-search-key
                    field="name"
                    :field-list="[
                        {
                            label: '昵称',
                            value: 'name'
                        },
                        {
                            label: '手机号',
                            value: 'phone'
                        }
                    ]"
                    refreshOnInput
                />
            </cl-row>
            <cl-row>
                <cl-table ref="Table" :auto-height="false"></cl-table>
            </cl-row>
            <cl-row>
                <cl-flex1 />
                <cl-pagination />
            </cl-row>
            <cl-upsert ref="Upsert"></cl-upsert>
            <cl-form ref="Form"></cl-form>
        </cl-crud>
    </div>
</template>
<script setup lang="tsx">
import { useTable, useForm, useUpsert, useCrud, useSearch } from "./hooks";
import { EditPen } from "@element-plus/icons-vue";
interface Data {
    name?: string;
    age?: number;
    [key: string]: any;
}
const Upsert = useUpsert<Data>({
    items: [
        {
            type: "tabs",
            props: {
                labels: [
                    {
                        label: "基础",
                        value: "A",
                        icon: EditPen
                    },
                    {
                        label: "高级",
                        value: "B"
                    }
                ]
            }
        },
        {
            group: "A",
            prop: "age",
            label: "年龄",
            component: {
                name: "el-input"
            }
        },
        {
            group: "A",
            prop: "name",
            label: "昵称",
            component: {
                name: "el-input"
            },
            hidden({ scope }) {
                return scope.age < 18;
            }
        },
        {
            group: "B",
            prop: "phone",
            label: "手机",
            component: {
                name: "el-input"
            },
            hidden({ scope }) {
                return scope.age < 18;
            }
        },
        () => {
            return {
                group: "A",
                hidden: Upsert.value?.mode == "add"
            };
        }
    ],
    onOpened(data) {
        console.log(data);
        Upsert.value?.setForm("age", "18");
    }
});
const Table = useTable<Data>(
    {
        contextMenu: [
            {
                label: "带图标",
                prefixIcon: EditPen
            },
            {
                label: "多层级",
                children: [
                    {
                        label: "A",
                        children: [
                            {
                                label: "A-1"
                            }
                        ]
                    },
                    {
                        label: "B"
                    }
                ]
            }
        ],
        columns: [
            {
                label: "姓名",
                prop: "name",
                search: {
                    component: {
                        name: "el-date-picker"
                    }
                }
            },
            {
                label: "手机号",
                prop: "phone",
                search: {
                    component: {
                        name: "el-date-picker"
                    }
                }
            },
            {
                type: "op"
            }
        ]
    },
    (table) => {
        console.log(table);
    }
);
const Crud = useCrud(
    {
        service: "test"
    },
    (app) => {
        app.refresh();
    }
);
const Form = useForm<Data>();
const Search = useSearch({
    collapse: true,
    resetBtn: true,
    items: [
        {
            label: "姓名",
            prop: "name",
            component: {
                name: "el-input"
            },
            hook: {
                reset() {
                    return [];
                }
            }
        }
    ]
});
</script>
<style scoped>
.title {
    text-align: center;
    font-size: 14px;
    font-weight: bold;
}
</style>
packages/crud/src/components/add-btn/index.tsx
New file
@@ -0,0 +1,21 @@
import { defineComponent } from "vue";
import { useConfig, useCore } from "../../hooks";
export default defineComponent({
    name: "cl-add-btn",
    setup(_, { slots }) {
        const { crud } = useCore();
        const { style } = useConfig();
        return () => {
            return (
                crud.getPermission("add") && (
                    <el-button type="primary" size={style.size} onClick={crud.rowAdd}>
                        {slots.default?.() || crud.dict.label.add}
                    </el-button>
                )
            );
        };
    }
});
packages/crud/src/components/adv/btn.tsx
New file
@@ -0,0 +1,31 @@
import { useConfig, useCore } from "../../hooks";
import { defineComponent } from "vue";
import { Search } from "@element-plus/icons-vue";
export default defineComponent({
    name: "cl-adv-btn",
    components: {
        Search
    },
    setup(_, { slots }) {
        const { crud, mitt } = useCore();
        const { style } = useConfig();
        function open() {
            mitt.emit("crud.openAdvSearch");
        }
        return () => {
            return (
                <el-button size={style.size} onClick={open} class="cl-adv-btn">
                    <el-icon>
                        <Search />
                    </el-icon>
                    {slots.default?.() || crud.dict.label.advSearch}
                </el-button>
            );
        };
    }
});
packages/crud/src/components/adv/search.tsx
New file
@@ -0,0 +1,203 @@
import { defineComponent, h, inject, mergeProps, nextTick, type PropType, reactive, ref } from "vue";
import { Close } from "@element-plus/icons-vue";
import { useBrowser, useConfig, useCore } from "../../hooks";
import { renderNode } from "../../utils/vnode";
import { useApi } from "../form/helper";
import { isArray } from "lodash-es";
export default defineComponent({
    name: "cl-adv-search",
    components: {
        Close
    },
    props: {
        // 表单项
        items: {
            type: Array as PropType<ClForm.Item[]>,
            default: () => []
        },
        // 标题
        title: String,
        // 窗体大小
        size: {
            type: [Number, String],
            default: "30%"
        },
        // 操作按钮
        op: {
            type: Array,
            default: () => ["clear", "reset", "close", "search"]
        },
        // 搜索钩子
        onSearch: Function
    },
    emits: ["reset", "clear"],
    setup(props, { emit, slots, expose }) {
        const { crud, mitt } = useCore();
        const { style } = useConfig();
        const browser = useBrowser();
        // 配置
        const config = reactive<ClAdvSearch.Config>(
            mergeProps(props, inject("useAdvSearch__options") || {})
        );
        // cl-form
        const Form = ref<ClForm.Ref>();
        // el-drawer
        const Drawer = ref();
        // 是否可见
        const visible = ref(false);
        // 打开
        function open() {
            visible.value = true;
            nextTick(() => {
                Form.value?.open({
                    items: config.items || [],
                    op: {
                        hidden: true
                    },
                    isReset: false
                });
            });
        }
        // 关闭
        function close() {
            Drawer.value.handleClose();
        }
        // 重置数据
        function reset() {
            const d: any = {};
            config.items?.map((e) => {
                if (typeof e.hook != 'string' && e.hook?.reset) {
                    const props = e.hook.reset(e.prop!)
                    if (isArray(props)) {
                        props.forEach((prop) => {
                            d[prop] = undefined;
                        })
                    }
                }
                d[e.prop!] = undefined;
            });
            // 重置表单
            Form.value?.reset();
            // 列表刷新
            search();
            // 重置事件
            emit("reset", d);
        }
        // 清空数据
        function clear() {
            Form.value?.clear();
            emit("clear");
        }
        // 搜素请求
        function search(params?: any) {
            const form = Form.value?.getForm();
            function next(data: any) {
                Form.value?.done();
                close();
                return crud.refresh({
                    ...data,
                    ...params,
                    page: 1
                });
            }
            if (config.onSearch) {
                config.onSearch(form, { next, close });
            } else {
                next(form);
            }
        }
        // 消息事件
        mitt.on("crud.openAdvSearch", open);
        // 渲染表单
        function renderForm() {
            return h(<cl-form ref={Form} inner enable-plugin={false} />, {}, slots);
        }
        // 渲染底部
        function renderFooter() {
            const fns = { search, reset, clear, close };
            return config.op?.map((e: string) => {
                switch (e) {
                    case "search":
                    case "reset":
                    case "clear":
                    case "close":
                        return h(
                            <el-button />,
                            {
                                type: e == "search" ? "primary" : null,
                                size: style.size,
                                onClick: () => {
                                    fns[e]()
                                }
                            },
                            { default: () => crud.dict.label[e] }
                        );
                    default:
                        return renderNode(e, {
                            scope: Form.value?.getForm(),
                            slots
                        });
                }
            });
        }
        expose({
            open,
            close,
            clear,
            ...useApi({ Form }),
            reset,
            Form
        });
        return () => {
            return (
                <el-drawer
                    ref={Drawer}
                    modal-class="cl-adv-search"
                    v-model={visible.value}
                    direction="rtl"
                    with-header={false}
                    size={browser.isMini ? "100%" : config.size}>
                    <div class="cl-adv-search__header">
                        <span class="text">{config.title || crud.dict.label.advSearch}</span>
                        <el-icon size={20} onClick={close}>
                            <Close />
                        </el-icon>
                    </div>
                    <div class="cl-adv-search__container">{renderForm()}</div>
                    <div class="cl-adv-search__footer">{renderFooter()}</div>
                </el-drawer>
            );
        };
    }
});
packages/crud/src/components/context-menu/index.tsx
New file
@@ -0,0 +1,282 @@
import {
    defineComponent,
    nextTick,
    onMounted,
    reactive,
    ref,
    h,
    render,
    toRaw,
    type PropType
} from "vue";
import { isString } from "lodash-es";
import { addClass, contains, removeClass } from "../../utils";
import { useRefs } from "../../hooks";
import { ElIcon } from "element-plus";
import { ArrowRight } from "@element-plus/icons-vue";
const ClContextMenu = defineComponent({
    name: "cl-context-menu",
    props: {
        show: Boolean,
        options: {
            type: Object as PropType<ClContextMenu.Options>,
            default: () => ({})
        },
        event: {
            type: Object,
            default: () => ({})
        }
    },
    setup(props, { expose, slots }) {
        const { refs, setRefs } = useRefs();
        // 是否可见
        const visible = ref(props.show || false);
        // 按钮列表
        const list = ref<ClContextMenu.Item[]>([]);
        // 样式
        const style = reactive({
            left: "0px",
            top: "0px"
        });
        // 选中值
        const ids = ref("");
        // 阻止默认事件
        function stopDefault(e: any) {
            if (e.preventDefault) {
                e.preventDefault();
            }
            if (e.stopPropagation) {
                e.stopPropagation();
            }
        }
        // 解析列表
        function parseList(list: ClContextMenu.Item[]) {
            function deep(list: ClContextMenu.Item[]) {
                list.forEach((e) => {
                    e.showChildren = false;
                    if (e.children) {
                        deep(e.children);
                    }
                });
            }
            deep(list);
            return list;
        }
        // 目标元素
        let targetEl: any;
        // 关闭
        function close() {
            visible.value = false;
            ids.value = "";
            if (targetEl) {
                removeClass(targetEl, "cl-context-menu__target");
            }
        }
        // 打开
        function open(event: any, options: ClContextMenu.Options = {}) {
            // 阻止默认事件
            stopDefault(event);
            // 显示
            visible.value = true;
            // 元素
            const el = refs["context-menu"].querySelector(".cl-context-menu__box") as HTMLElement;
            // 点击样式
            if (options?.hover) {
                const d = options.hover === true ? {} : options.hover;
                targetEl = event.target;
                if (targetEl && isString(targetEl.className)) {
                    if (d.target) {
                        while (!targetEl.className.includes(d.target)) {
                            targetEl = targetEl.parentNode;
                        }
                    }
                    addClass(targetEl, d.className || "cl-context-menu__target");
                }
            }
            // 自定义样式
            if (options?.class) {
                addClass(el, options.class);
            }
            // 菜单列表
            if (options?.list) {
                list.value = parseList(options.list);
            }
            nextTick(() => {
                // 计算位置
                let left = event.pageX;
                let top = event.pageY;
                // 组件方式用 offset 计算
                if (!props.show) {
                    left = event.offsetX;
                    top = event.offsetY;
                }
                const { clientHeight: h1, clientWidth: w1 } = event.target?.ownerDocument.body;
                const { clientHeight: h2, clientWidth: w2 } = el;
                if (top + h2 > h1) {
                    top = h1 - h2 - 5;
                }
                if (left + w2 > w1) {
                    left = w1 - w2 - 5;
                }
                style.left = left + "px";
                style.top = top + "px";
            })
            return {
                close
            };
        }
        // 行点击
        function rowClick(item: ClContextMenu.Item, id: string) {
            ids.value = id;
            if (item.disabled) {
                return false;
            }
            if (item.callback) {
                return item.callback(close);
            }
            if (item.children) {
                item.showChildren = !item.showChildren;
            } else {
                close();
            }
        }
        expose({
            open,
            close
        });
        onMounted(function () {
            if (visible.value) {
                const { body, documentElement } = props.event.target.ownerDocument;
                // 添加到 body 下
                body.appendChild(refs["context-menu"]);
                // 关闭事件
                (documentElement || body).addEventListener("mousedown", (e: any) => {
                    const el = refs["context-menu"];
                    if (!contains(el, e.target) && el != e.target) {
                        close();
                    }
                });
                // 默认打开
                open(props.event, props?.options);
            }
        });
        return () => {
            function deep(list: ClContextMenu.Item[], pId: string, level: number) {
                return (
                    <div class={["cl-context-menu__box", level > 1 && "is-append"]}>
                        {list
                            .filter((e) => !e.hidden)
                            .map((e, i) => {
                                const id = `${pId}-${i}`;
                                if (!e.suffixIcon) {
                                    // 默认图标
                                    if (e.children) {
                                        e.suffixIcon = ArrowRight;
                                    }
                                }
                                return (
                                    <div
                                        class={{
                                            "is-active": ids.value.includes(id),
                                            "is-ellipsis": e.ellipsis ?? true,
                                            "is-disabled": e.disabled
                                        }}
                                        onClick={(ev: MouseEvent) => {
                                            rowClick(e, id);
                                            ev.stopPropagation();
                                        }}
                                    >
                                        {/* 前缀图标 */}
                                        {e.prefixIcon && <ElIcon>{h(toRaw(e.prefixIcon))}</ElIcon>}
                                        {/* 标题 */}
                                        <span>
                                            {e.label}
                                        </span>
                                        {/* 后缀图标 */}
                                        {e.suffixIcon && <ElIcon>{h(toRaw(e.suffixIcon))}</ElIcon>}
                                        {/* 子集 */}
                                        {e.children &&
                                            e.showChildren &&
                                            deep(e.children, id, level + 1)}
                                    </div>
                                );
                            })}
                    </div>
                );
            }
            return (
                visible.value && (
                    <div
                        class="cl-context-menu"
                        ref={setRefs("context-menu")}
                        style={style}
                        onContextmenu={stopDefault}>
                        {slots.default ? slots.default() : deep(list.value, "0", 1)}
                    </div>
                )
            );
        };
    }
});
export const ContextMenu = {
    open(event: any, options: ClContextMenu.Options) {
        const vm = h(ClContextMenu, {
            show: true,
            event,
            options
        });
        render(vm, event.target.ownerDocument.createElement("div"));
        return vm.component?.exposed as ClContextMenu.Exposed;
    }
};
export default ClContextMenu;
packages/crud/src/components/crud/helper.ts
New file
@@ -0,0 +1,287 @@
import { ElMessageBox, ElMessage } from "element-plus";
import { Mitt } from "../../utils/mitt";
import { ref } from "vue";
import { assign, isArray, isFunction } from "lodash-es";
import { merge } from "../../utils";
interface Options {
    mitt: Mitt;
    config: ClCrud.Config;
    crud: ClCrud.Ref;
}
export function useHelper({ config, crud, mitt }: Options) {
    // 刷新随机值,避免脏数据
    const refreshRd = ref(0);
    // 获取权限
    function getPermission(key: "page" | "list" | "info" | "update" | "add" | "delete"): boolean {
        return Boolean(crud.permission[key]);
    }
    // 根据字典替换请求参数
    function paramsReplace(params: obj) {
        const { pagination, search, sort } = crud.dict;
        // 请求参数
        const a: any = { ...params };
        // 字典
        const b: any = { ...pagination, ...search, ...sort };
        for (const i in b) {
            if (a[i]) {
                if (i != b[i]) {
                    a[`_${b[i]}`] = a[i];
                    delete a[i];
                }
            }
        }
        for (const i in a) {
            if (i[0] === "_") {
                a[i.substr(1)] = a[i];
                delete a[i];
            }
        }
        return a;
    }
    // 刷新请求
    function refresh(params?: obj) {
        const { service, dict } = crud;
        return new Promise((success, error) => {
            // 合并请求参数
            const reqParams = paramsReplace(assign(crud.params, params));
            // Loading
            crud.loading = true;
            // 预防脏数据
            const rd = (refreshRd.value = Math.random());
            // 完成事件
            function done() {
                crud.loading = false;
            }
            // 渲染
            function render(data: any | any[], pagination?: any) {
                const res = isArray(data) ? { list: data, pagination } : data;
                done();
                success(res);
                mitt.emit("crud.refresh", res);
            }
            // 下一步
            function next(params: obj): Promise<any> {
                return new Promise(async (resolve, reject) => {
                    await service[dict.api.page](params)
                        .then((res) => {
                            if (rd != refreshRd.value) {
                                return false;
                            }
                            if (isArray(res)) {
                                res = {
                                    list: res,
                                    pagination: {
                                        total: res.length
                                    }
                                };
                            }
                            render(res);
                            resolve(res);
                        })
                        .catch((err) => {
                            ElMessage.error(err.message);
                            error(err);
                            reject(err);
                        });
                    done();
                });
            }
            // 刷新钩子
            if (config.onRefresh) {
                config.onRefresh(reqParams, { next, done, render });
            } else {
                next(reqParams);
            }
        });
    }
    // 打开详情
    function rowInfo(data: any) {
        mitt.emit("crud.proxy", {
            name: "info",
            data: [data]
        });
    }
    // 打开新增
    function rowAdd() {
        mitt.emit("crud.proxy", {
            name: "add"
        });
    }
    // 打开编辑
    function rowEdit(data: any) {
        mitt.emit("crud.proxy", {
            name: "edit",
            data: [data]
        });
    }
    // 打开追加
    function rowAppend(data: any) {
        mitt.emit("crud.proxy", {
            name: "append",
            data: [data]
        });
    }
    // 关闭新增、编辑弹窗
    function rowClose() {
        mitt.emit("crud.proxy", {
            name: "close"
        });
    }
    // 删除请求
    function rowDelete(...selection: any[]) {
        const { service, dict } = crud;
        // 参数
        const params = {
            ids: selection.map((e) => e[dict.primaryId])
        };
        // 下一步
        async function next(data: obj) {
            return new Promise((resolve, reject) => {
                ElMessageBox({
                    type: "warning",
                    title: dict.label.tips,
                    message: dict.label.deleteConfirm,
                    confirmButtonText: dict.label.confirm,
                    cancelButtonText: dict.label.close,
                    showCancelButton: true,
                    async beforeClose(action, instance, done) {
                        if (action === "confirm") {
                            instance.confirmButtonLoading = true;
                            await service[dict.api.delete]({ ...params, ...data })
                                .then((res) => {
                                    ElMessage.success(dict.label.deleteSuccess);
                                    refresh();
                                    resolve(res);
                                })
                                .catch((err) => {
                                    ElMessage.error(err.message);
                                    reject(err);
                                });
                            instance.confirmButtonLoading = false;
                        }
                        done();
                    }
                }).catch(() => null);
            });
        }
        // 删除钩子
        if (config.onDelete) {
            config.onDelete(selection, { next });
        } else {
            next(params);
        }
    }
    // 代理
    function proxy(name: string, data?: any[]) {
        mitt.emit("crud.proxy", {
            name,
            data
        });
    }
    // 获取请求参数
    function getParams() {
        return crud.params;
    }
    // 替换请求参数
    function setParams(data: obj) {
        merge(crud.params, data);
    }
    // 设置
    function set(key: string, value: any) {
        if (!value) {
            return false;
        }
        switch (key) {
            // 服务
            case "service":
                Object.assign(crud.service, value);
                crud.service.__proto__ = value.__proto__;
                if (value._permission) {
                    for (const i in value._permission) {
                        crud.permission[i] = value._permission[i];
                    }
                }
                break;
            // 权限
            case "permission":
                if (isFunction(value)) {
                    merge(crud.permission, value(crud));
                } else {
                    merge(crud.permission, value);
                }
                break;
            default:
                merge(crud[key], value);
                break;
        }
    }
    // 监听事件
    function on(name: string, callback: fn) {
        mitt.on(`${name}-${crud.id}`, callback);
    }
    // 默认值
    set("dict", config.dict);
    set("service", config.service);
    set("permission", config.permission);
    return {
        proxy,
        set,
        on,
        rowInfo,
        rowAdd,
        rowEdit,
        rowAppend,
        rowDelete,
        rowClose,
        refresh,
        getPermission,
        paramsReplace,
        getParams,
        setParams
    };
}
packages/crud/src/components/crud/index.tsx
New file
@@ -0,0 +1,91 @@
import { defineComponent, getCurrentInstance, inject, provide, reactive } from "vue";
import { cloneDeep } from "lodash-es";
import { useHelper } from "./helper";
import { Mitt } from "../../utils/mitt";
import { mergeConfig, merge } from "../../utils";
import { crudList } from "../../emitter";
import { useConfig } from "../../hooks";
export default defineComponent({
    name: "cl-crud",
    props: {
        // 组件名
        name: String,
        // 是否有边框
        border: Boolean,
        // 内间距
        padding: {
            type: String,
            default: "10px"
        }
    },
    setup(props, { slots, expose }) {
        // 当前实例
        const inst = getCurrentInstance();
        // 配置
        const config = reactive<ClCrud.Config>(mergeConfig(inject("useCrud__options") || {}));
        // 事件
        const mitt = new Mitt(inst?.uid);
        // 全局配置
        const { dict, permission } = useConfig();
        // 参数
        const crud = reactive(
            merge(
                {
                    id: props.name || inst?.uid,
                    // 绑定的路由地址
                    routePath: location.pathname || "/",
                    // 表格加载状态
                    loading: false,
                    // 表格已选列
                    selection: [],
                    // 请求参数
                    params: {
                        page: 1,
                        size: 20
                    },
                    // 请求服务
                    service: {},
                    // 字典
                    dict: {},
                    // 权限
                    permission: {},
                    // 事件
                    mitt,
                    // 配置
                    config
                },
                cloneDeep({ dict, permission })
            )
        );
        // 追加参数
        merge(crud, useHelper({ config, crud, mitt }));
        // 集合
        crudList.push(crud);
        // 值穿透
        provide("crud", crud);
        provide("mitt", mitt);
        // 导出
        expose(crud);
        return () => {
            return (
                <div
                    class={["cl-crud", { "is-border": props.border }]}
                    style={{ padding: props.padding }}>
                    {slots.default?.()}
                </div>
            );
        };
    }
});
packages/crud/src/components/dialog/index.tsx
New file
@@ -0,0 +1,288 @@
import { defineComponent, h, ref, watch, computed, provide } from "vue";
import { Close, FullScreen, Minus } from "@element-plus/icons-vue";
import { renderNode } from "../../utils/vnode";
import { isArray, isBoolean } from "lodash-es";
import { useBrowser } from "../../hooks";
export default defineComponent({
    name: "cl-dialog",
    components: {
        Close,
        FullScreen,
        Minus
    },
    props: {
        // 是否可见
        modelValue: {
            type: Boolean,
            default: false
        },
        // Extraneous non-props attributes
        props: Object,
        // 标题
        title: {
            type: String,
            default: "-"
        },
        // 高度
        height: String,
        // 宽度
        width: {
            type: String,
            default: "50%"
        },
        // 內间距
        padding: {
            type: String,
            default: "20px"
        },
        // 是否缓存
        keepAlive: Boolean,
        // 是否全屏
        fullscreen: Boolean,
        // 控制按钮
        controls: {
            type: Array,
            default: () => ["fullscreen", "close"]
        },
        // 隐藏头部元素
        hideHeader: Boolean,
        // 关闭前
        beforeClose: Function,
        // 是否需要滚动条
        scrollbar: {
            type: Boolean,
            default: true
        },
        // 背景透明
        transparent: Boolean
    },
    emits: ["update:modelValue", "fullscreen-change"],
    setup(props, { emit, expose, slots }) {
        const browser = useBrowser();
        // el-dialog
        const Dialog = ref();
        // 是否全屏
        const fullscreen = ref(false);
        // 是否可见
        const visible = ref(false);
        // 缓存数
        const cacheKey = ref(0);
        // 是否全屏
        const isFullscreen = computed(() => {
            return browser && browser.isMini ? true : fullscreen.value;
        });
        // 监听绑定值
        watch(
            () => props.modelValue,
            (val) => {
                visible.value = val;
                if (val && !props.keepAlive) {
                    cacheKey.value += 1;
                }
            },
            {
                immediate: true
            }
        );
        // 监听 fullscreen 变化
        watch(
            () => props.fullscreen,
            (val) => {
                fullscreen.value = val;
            },
            {
                immediate: true
            }
        );
        // fullscreen-change 回调
        watch(fullscreen, (val: boolean) => {
            emit("fullscreen-change", val);
        });
        // 提供
        provide("dialog", {
            visible,
            fullscreen: isFullscreen
        });
        // 打开
        function open() {
            fullscreen.value = true;
        }
        // 关闭
        function close() {
            function done() {
                onClose();
            }
            if (props.beforeClose) {
                props.beforeClose(done);
            } else {
                done();
            }
        }
        // 关闭后
        function onClose() {
            emit("update:modelValue", false);
        }
        // 切换全屏
        function changeFullscreen(val?: boolean) {
            fullscreen.value = isBoolean(val) ? Boolean(val) : !fullscreen.value;
        }
        // 双击全屏
        function dblClickFullscreen() {
            if (isArray(props.controls) && props.controls.includes("fullscreen")) {
                changeFullscreen();
            }
        }
        // 渲染头部
        function renderHeader() {
            return (
                props.hideHeader || (
                    <div class="cl-dialog__header" onDblclick={dblClickFullscreen}>
                        <span class="cl-dialog__title">{props.title}</span>
                        <div class="cl-dialog__controls">
                            {props.controls.map((e: any) => {
                                switch (e) {
                                    //全屏按钮
                                    case "fullscreen":
                                        if (browser.screen === "xs") {
                                            return null;
                                        }
                                        // 是否显示全屏按钮
                                        if (isFullscreen.value) {
                                            return (
                                                <button
                                                    type="button"
                                                    class="minimize"
                                                    onClick={() => {
                                                        changeFullscreen(false);
                                                    }}>
                                                    <el-icon>
                                                        <Minus />
                                                    </el-icon>
                                                </button>
                                            );
                                        } else {
                                            return (
                                                <button
                                                    type="button"
                                                    class="maximize"
                                                    onClick={() => {
                                                        changeFullscreen(true);
                                                    }}>
                                                    <el-icon>
                                                        <FullScreen />
                                                    </el-icon>
                                                </button>
                                            );
                                        }
                                    // 关闭按钮
                                    case "close":
                                        return (
                                            <button type="button" class="close" onClick={close}>
                                                <el-icon>
                                                    <Close />
                                                </el-icon>
                                            </button>
                                        );
                                    // 自定义按钮
                                    default:
                                        return renderNode(e, {
                                            slots
                                        });
                                }
                            })}
                        </div>
                    </div>
                )
            );
        }
        expose({
            Dialog,
            visible,
            isFullscreen,
            open,
            close,
            changeFullscreen
        });
        return () => {
            return h(
                <el-dialog
                    ref={Dialog}
                    class={["cl-dialog", { 'is-transparent': props.transparent }]}
                    width={props.width}
                    beforeClose={props.beforeClose}
                    show-close={false}
                    append-to-body
                    fullscreen={isFullscreen.value}
                    v-model={visible.value}
                    onClose={onClose}
                />,
                {},
                {
                    header() {
                        return renderHeader();
                    },
                    default() {
                        const height = isFullscreen.value ? "100%" : props.height;
                        const style = {
                            padding: props.padding,
                            height
                        };
                        function content() {
                            return (
                                <div class="cl-dialog__default" style={style} key={cacheKey.value}>
                                    {slots.default?.()}
                                </div>
                            );
                        }
                        if (props.scrollbar) {
                            style.height = "auto";
                            return <el-scrollbar height={height}>{content()}</el-scrollbar>;
                        } else {
                            return content();
                        }
                    },
                    footer() {
                        const d = slots.footer?.();
                        if (d && d[0]?.shapeFlag) {
                            return <div class="cl-dialog__footer">{d}</div>;
                        }
                        return null;
                    }
                }
            );
        };
    }
});
packages/crud/src/components/error-message/index.tsx
New file
@@ -0,0 +1,17 @@
import { defineComponent } from "vue";
export default defineComponent({
    name: "cl-error-message",
    props: {
        title: String
    },
    setup(props) {
        return () => {
            return <div class="cl-error-message">
                {props.title}
            </div>;
        };
    }
});
packages/crud/src/components/filter/index.tsx
New file
@@ -0,0 +1,23 @@
import { defineComponent } from "vue";
export default defineComponent({
    name: "cl-filter",
    props: {
        label: String
    },
    setup(props, { slots }) {
        return () => {
            return (
                <div class="cl-filter">
                    <span class="cl-filter__label" v-show={props.label}>
                        {props.label}
                    </span>
                    {slots.default?.()}
                </div>
            );
        };
    }
});
packages/crud/src/components/flex1/index.tsx
New file
@@ -0,0 +1,11 @@
import { defineComponent } from "vue";
export default defineComponent({
    name: "cl-flex1",
    setup() {
        return () => {
            return <div class="cl-flex1" />;
        };
    }
});
packages/crud/src/components/form-card/index.tsx
New file
@@ -0,0 +1,51 @@
import { defineComponent, ref } from "vue";
import { ArrowDown, ArrowUp } from "@element-plus/icons-vue";
export default defineComponent({
    name: "cl-form-card",
    components: {
        ArrowDown,
        ArrowUp
    },
    props: {
        label: String,
        // 展开状态
        expand: {
            type: Boolean,
            default: true
        },
        // 是否能展开、收起
        isExpand: {
            type: Boolean,
            default: true
        }
    },
    setup(props, { slots }) {
        const visible = ref(props.expand);
        function toExpand() {
            if (props.isExpand) {
                visible.value = !visible.value;
            }
        }
        return () => {
            return (
                <div class={["cl-form-card", { "is-expand": visible.value }]}>
                    <div class="cl-form-card__header" v-show={props.label} onClick={toExpand}>
                        <span>{props.label}</span>
                        <el-icon v-show={props.isExpand}>
                            <arrow-down v-show={!visible.value} />
                            <arrow-up v-show={visible.value} />
                        </el-icon>
                    </div>
                    <div class="cl-form-card__container">{slots.default?.()}</div>
                </div>
            );
        };
    }
});
packages/crud/src/components/form-tabs/index.tsx
New file
@@ -0,0 +1,145 @@
import {
    defineComponent,
    h,
    nextTick,
    onMounted,
    PropType,
    reactive,
    ref,
    toRaw,
    watch
} from "vue";
import { isEmpty } from "lodash-es";
import { useRefs, useDialog } from "../../hooks";
export default defineComponent({
    name: "cl-form-tabs",
    props: {
        modelValue: [String, Number],
        labels: {
            type: Array,
            default: () => []
        },
        justify: {
            type: String as PropType<
                "start" | "end" | "left" | "right" | "center" | "justify" | "match-parent"
            >,
            default: "center"
        },
        type: {
            type: String as PropType<"card" | "default">,
            default: "default"
        }
    },
    emits: ["update:modelValue", "change"],
    setup(props, { emit, expose }) {
        const { refs, setRefs } = useRefs();
        // 标识
        const active = ref("");
        // 切换列表
        const list = ref<any[]>([]);
        // 下划线
        const line = reactive({
            width: "",
            offsetLeft: "",
            transform: "",
            backgroundColor: ""
        });
        function update(val: any) {
            if (!val) {
                return false;
            }
            nextTick(() => {
                const index = list.value.findIndex((e) => e.value === val);
                const item = refs[`tab-${index}`];
                if (item) {
                    // 下划线位置
                    line.width = item.offsetWidth + "px";
                    line.transform = `translateX(${item.offsetLeft}px)`;
                    // 靠左位置
                    let left = item.offsetLeft + item.clientWidth / 2 - 414 / 2 + 15;
                    if (left < 0) {
                        left = 0;
                    }
                    // 设置滚动距离
                    refs.tabs.scrollLeft = left;
                }
            });
            active.value = val;
            emit("update:modelValue", val);
        }
        // 监听绑定值变化
        watch(() => props.modelValue, update);
        // 监听值修改
        watch(
            () => active.value,
            (val) => {
                emit("change", val);
            }
        );
        useDialog({
            onFullscreen() {
                update(active.value);
            }
        });
        onMounted(function () {
            if (!isEmpty(props.labels)) {
                list.value = props.labels;
                update(isEmpty(props.modelValue) ? list.value[0].value : props.modelValue);
            }
        });
        expose({
            active,
            list,
            line,
            update
        });
        return () => {
            return (
                <div class={["cl-form-tabs", `cl-form-tabs--${props.type}`]}>
                    <div
                        class="cl-form-tabs__wrap"
                        style={{ textAlign: props.justify }}
                        ref={setRefs("tabs")}>
                        <ul>
                            {list.value.map((e, i) => {
                                return (
                                    <li
                                        ref={setRefs(`tab-${i}`)}
                                        class={{ "is-active": e.value === active.value }}
                                        onClick={() => {
                                            update(e.value);
                                        }}>
                                        {e.icon && <el-icon>{h(toRaw(e.icon))}</el-icon>}
                                        <span>{e.label}</span>
                                    </li>
                                );
                            })}
                            {line.width && <div class="cl-form-tabs__line" style={line}></div>}
                        </ul>
                    </div>
                </div>
            );
        };
    }
});
packages/crud/src/components/form/helper/action.ts
New file
@@ -0,0 +1,146 @@
import { assign } from "lodash-es";
import { dataset } from "../../../utils";
export function useAction({
    config,
    form,
    Form
}: {
    config: ClForm.Config;
    form: obj;
    Form: Vue.Ref<any>;
}) {
    // 设置数据
    function set(
        {
            prop,
            key,
            path
        }: { prop?: string; key?: "options" | "props" | "hidden" | "hidden-toggle"; path?: string },
        data?: any
    ) {
        const p: string = path || "";
        if (path) {
            dataset(config, p, data);
        } else {
            let d: any;
            if (prop) {
                function deep(arr: ClForm.Item[]) {
                    arr.forEach((e) => {
                        if (e.prop == prop) {
                            d = e;
                        } else {
                            if (e.children) {
                                deep(e.children);
                            }
                        }
                    });
                }
                deep(config.items);
            }
            if (d) {
                switch (key) {
                    case "options":
                        d.component.options = data;
                        break;
                    case "props":
                        assign(d.component.props, data);
                        break;
                    case "hidden":
                        d.hidden = data;
                        break;
                    case "hidden-toggle":
                        d.hidden = data === undefined ? !d.hidden : !data;
                        break;
                    default:
                        assign(d, data);
                        break;
                }
            } else {
                console.error(`[set] ${prop} is not found`);
            }
        }
    }
    // 获取表单值
    function getForm(prop: string) {
        return prop ? form[prop] : form;
    }
    // 设置表单值
    function setForm(prop: string, value: any) {
        form[prop] = value;
    }
    // 设置配置
    function setConfig(path: string, value: any) {
        set({ path }, value);
    }
    // 设置数据
    function setData(prop: string, value: any) {
        set({ prop }, value);
    }
    // 设置表单项的下拉数据列表
    function setOptions(prop: string, value: any[]) {
        set({ prop, key: "options" }, value);
    }
    // 设置表单项的组件参数
    function setProps(prop: string, value: any) {
        set({ prop, key: "props" }, value);
    }
    // 切换表单项的显示、隐藏
    function toggleItem(prop: string, value?: boolean) {
        set({ prop, key: "hidden-toggle" }, value);
    }
    // 对部分表单项隐藏
    function hideItem(...props: string[]) {
        props.forEach((prop) => {
            set({ prop, key: "hidden" }, true);
        });
    }
    // 对部分表单项显示
    function showItem(...props: string[]) {
        props.forEach((prop) => {
            set({ prop, key: "hidden" }, false);
        });
    }
    // 设置标题
    function setTitle(value: string) {
        config.title = value;
    }
    // 是否展开表单项
    function collapseItem(e: any) {
        Form.value?.clearValidate(e.prop);
        e.collapse = !e.collapse;
    }
    return {
        getForm,
        setForm,
        setData,
        setConfig,
        setOptions,
        setProps,
        toggleItem,
        hideItem,
        showItem,
        setTitle,
        collapseItem
    };
}
packages/crud/src/components/form/helper/api.ts
New file
@@ -0,0 +1,36 @@
import { useElApi } from "../../../hooks";
export function useApi({ Form }: { Form: Vue.Ref<any> }) {
    return useElApi(
        [
            "open",
            "close",
            "clear",
            "reset",
            "submit",
            "bindForm",
            "changeTab",
            "setTitle",
            "showLoading",
            "hideLoading",
            "collapseItem",
            "getForm",
            "setForm",
            "invokeData",
            "setData",
            "setConfig",
            "setOptions",
            "setProps",
            "toggleItem",
            "hideItem",
            "showItem",
            "validate",
            "validateField",
            "resetFields",
            "scrollToField",
            "clearValidate",
            "fields"
        ],
        Form
    );
}
packages/crud/src/components/form/helper/index.ts
New file
@@ -0,0 +1,85 @@
import { reactive, ref, watch } from "vue";
import { useConfig } from "../../../hooks";
import { cloneDeep } from "lodash-es";
export function useForm() {
    const { dict } = useConfig();
    // 表单配置
    const config = reactive<ClForm.Config>({
        title: "-",
        height: undefined,
        width: "50%",
        props: {
            labelWidth: 100
        },
        on: {},
        op: {
            hidden: false,
            saveButtonText: dict.label.save,
            closeButtonText: dict.label.close,
            buttons: ["close", "save"]
        },
        dialog: {
            closeOnClickModal: false,
            appendToBody: true
        },
        items: [],
        form: {},
        _data: {}
    });
    const Form = ref();
    // 表单数据
    const form = reactive<obj>({});
    // 表单数据备份
    const oldForm = ref<obj>({});
    // 表单是否可见
    const visible = ref(false);
    // 表单提交保存状态
    const saving = ref(false);
    // 表单加载状态
    const loading = ref(false);
    // 表单禁用状态
    const disabled = ref(false);
    // 监听表单变化
    watch(
        () => form,
        (val) => {
            if (config.on?.change) {
                for (const i in val) {
                    if (form[i] !== oldForm.value[i]) {
                        config.on?.change(val, i);
                    }
                }
            }
            oldForm.value = cloneDeep(val);
        },
        {
            deep: true
        }
    );
    return {
        Form,
        config,
        form,
        visible,
        saving,
        loading,
        disabled
    };
}
export * from "./action";
export * from "./api";
export * from "./plugins";
export * from "./tabs";
packages/crud/src/components/form/helper/plugins.ts
New file
@@ -0,0 +1,92 @@
import { type Ref, type WatchStopHandle, getCurrentInstance, watch } from "vue";
import { useConfig } from "../../../hooks";
import { uniqueFns } from "../../../utils";
export function usePlugins(enable: boolean, { visible }: { visible: Ref<boolean> }) {
    const that: any = getCurrentInstance();
    const { style } = useConfig();
    interface Event {
        onOpen: (() => void)[];
        onClose: (() => void)[];
        onSubmit: ((data: obj) => Promise<obj> | obj)[];
        [key: string]: any;
    }
    // 事件
    const ev: Event = {
        onOpen: [],
        onClose: [],
        onSubmit: []
    };
    // 监听器
    let timer: WatchStopHandle | null = null;
    // 插件创建
    function create(plugins: ClForm.Plugin[] = []) {
        if (!enable) {
            return false;
        }
        for (const i in ev) {
            ev[i] = [];
        }
        // 停止监听
        if (timer) {
            timer();
        }
        // 执行
        uniqueFns([...(style.form.plugins || []), ...plugins]).forEach((p) => {
            const d: any = {
                exposed: that.exposed
            };
            for (const i in ev) {
                d[i] = (cb: any) => {
                    ev[i].push(cb);
                };
            }
            p(d);
        });
        timer = watch(
            visible,
            (val) => {
                if (val) {
                    setTimeout(() => {
                        ev.onOpen.forEach((e) => e());
                    }, 10);
                } else {
                    ev.onClose.forEach((e) => e());
                }
            },
            {
                immediate: true
            }
        );
    }
    // 表单提交
    async function submit(data: any) {
        let d = data;
        for (let i = 0; i < ev.onSubmit.length; i++) {
            const d2 = await ev.onSubmit[i](d);
            if (d2) {
                d = d2;
            }
        }
        return d;
    }
    return {
        create,
        submit
    };
}
packages/crud/src/components/form/helper/tabs.ts
New file
@@ -0,0 +1,151 @@
import { computed, ref } from "vue";
export function useTabs({ config, Form }: { config: ClForm.Config; Form: Vue.Ref<any> }) {
    // 选中
    const active = ref<string | undefined>();
    // 列表
    const list = computed(() => {
        return get()?.props?.labels || [];
    });
    // 获取选项
    function getItem(value: any) {
        return list.value.find((e) => e.value == value);
    }
    // 是否已加载
    function isLoaded(value: any) {
        const d = getItem(value);
        return d?.lazy ? d.loaded : true;
    }
    // 加载后
    function onLoad(value: any) {
        const d = getItem(value);
        d!.loaded = true;
    }
    // 查找分组
    function toGroup(opts: { config: ClForm.Config; prop: string; refs: any }) {
        if (active.value) {
            let name;
            // 查找标签上绑定的数据
            const el = opts.refs.form.querySelector(`[data-prop="${opts.prop}"]`);
            // 各自判断
            if (el) {
                name = el?.getAttribute("data-group");
            } else {
                function deep(d: ClForm.Item) {
                    if (d.prop == opts.prop) {
                        name = d.group;
                    } else {
                        if (d.children) {
                            d.children.forEach(deep);
                        }
                    }
                }
                config.items.forEach(deep);
            }
            if (name) {
                set(name);
            }
        }
    }
    // 获取参数
    function get() {
        return config.items.find((e) => e.type === "tabs");
    }
    // 设置参数
    function set(data: any) {
        active.value = data;
    }
    // 清空
    function clear() {
        // 清空选中
        active.value = undefined;
        // 清空加载状态
        list.value.forEach((e) => {
            if (e.lazy && e.loaded) {
                e.loaded = undefined;
            }
        });
    }
    // 切换
    function change(value: any, isValid = true) {
        return new Promise((resolve: Function, reject: Function) => {
            function next() {
                active.value = value;
                resolve();
            }
            if (isValid) {
                let isError = false;
                const arr = config.items
                    .filter((e) => e.group == active.value && !e._hidden && e.prop)
                    .map((e) => {
                        return new Promise((r: Function) => {
                            // 验证表单
                            Form.value.validateField(e.prop, (valid: string) => {
                                if (valid) {
                                    isError = true;
                                }
                                r(valid);
                            });
                        });
                    });
                Promise.all(arr).then((msg) => {
                    if (isError) {
                        reject(msg.filter(Boolean));
                    } else {
                        next();
                    }
                });
            } else {
                next();
            }
        });
    }
    // 合并
    function mergeProp(item: ClForm.Item) {
        const d = get();
        if (d && d.props) {
            const { mergeProp, labels = [] } = d.props;
            if (mergeProp) {
                const t = labels.find((e) => e.value == item.group);
                if (t && t.name) {
                    item.prop = `${t.name}-${item.prop}`;
                }
            }
        }
    }
    return {
        active,
        list,
        isLoaded,
        onLoad,
        get,
        set,
        change,
        clear,
        mergeProp,
        toGroup
    };
}
packages/crud/src/components/form/index.tsx
New file
@@ -0,0 +1,683 @@
import { defineComponent, h, nextTick } from "vue";
import { assign, cloneDeep, isBoolean, isFunction, keys } from "lodash-es";
import { useAction, useForm, usePlugins, useTabs } from "./helper";
import { useBrowser, useConfig, useElApi, useRefs } from "../../hooks";
import { getValue, merge } from "../../utils";
import formHook from "../../utils/form-hook";
import { renderNode } from "../../utils/vnode";
export default defineComponent({
    name: "cl-form",
    props: {
        name: String,
        inner: Boolean,
        inline: Boolean,
        enablePlugin: {
            type: Boolean,
            default: true
        }
    },
    setup(props, { expose, slots }) {
        const { refs, setRefs } = useRefs();
        const { style, dict } = useConfig();
        const browser = useBrowser();
        const { Form, config, form, visible, saving, loading, disabled } = useForm();
        // 关闭的操作类型
        let closeAction: ClForm.CloseAction = "close";
        // 旧表单数据
        let defForm: obj | undefined;
        // 选项卡
        const Tabs = useTabs({ config, Form });
        // 操作
        const Action = useAction({ config, form, Form });
        // 方法
        const ElFormApi = useElApi(
            [
                "validate",
                "validateField",
                "resetFields",
                "scrollToField",
                "clearValidate",
                "fields"
            ],
            Form
        );
        // 插件
        const plugin = usePlugins(props.enablePlugin, { visible });
        // 显示加载中
        function showLoading() {
            loading.value = true;
        }
        // 隐藏加载
        function hideLoading() {
            loading.value = false;
        }
        // 设置是否禁用
        function setDisabled(val: boolean = true) {
            disabled.value = val;
        }
        // 请求表单保存状态
        function done() {
            saving.value = false;
        }
        // 关闭表单
        function close(action?: ClForm.CloseAction) {
            if (action) {
                closeAction = action;
            }
            beforeClose(() => {
                visible.value = false;
                done();
            });
        }
        // 关闭前
        function beforeClose(done: fn) {
            if (config.on?.close) {
                config.on.close(closeAction, done);
            } else {
                done();
            }
        }
        // 关闭后
        function onClosed() {
            Tabs.clear();
            Form.value?.clearValidate();
        }
        // 清空表单验证
        function clear() {
            for (const i in form) {
                delete form[i];
            }
            setTimeout(() => {
                Form.value?.clearValidate();
            }, 0);
        }
        // 重置
        function reset() {
            if (defForm) {
                for (const i in defForm) {
                    form[i] = cloneDeep(defForm[i]);
                }
            }
        }
        // 转换表单值,处理多层级等数据
        function invokeData(d: any) {
            for (const i in d) {
                if (i.includes("-")) {
                    // 结构参数
                    const [a, ...arr] = i.split("-");
                    // 关键值的key
                    const k: string = arr.pop() || "";
                    if (!d[a]) {
                        d[a] = {};
                    }
                    let f: any = d[a];
                    // 设置默认值
                    arr.forEach((e) => {
                        if (!f[e]) {
                            f[e] = {};
                        }
                        f = f[e];
                    });
                    // 设置关键值
                    f[k] = d[i];
                    delete d[i];
                }
            }
        }
        // 表单提交
        function submit(callback?: fn) {
            // 验证表单
            Form.value.validate(async (valid: boolean, error: any) => {
                if (valid) {
                    saving.value = true;
                    // 拷贝表单值
                    const d = cloneDeep(form);
                    config.items.forEach((e) => {
                        function deep(e: ClForm.Item) {
                            if (e.prop) {
                                // 过滤隐藏的表单项
                                if (e._hidden) {
                                    if (e.prop) {
                                        delete d[e.prop];
                                    }
                                }
                                // hook 提交处理
                                if (e.hook) {
                                    formHook.submit({
                                        ...e,
                                        value: e.prop ? d[e.prop] : undefined,
                                        form: d
                                    });
                                }
                            }
                            if (e.children) {
                                e.children.forEach(deep);
                            }
                        }
                        deep(e);
                    });
                    // 处理数据
                    invokeData(d);
                    const submit = callback || config.on?.submit;
                    // 提交事件
                    if (submit) {
                        submit(await plugin.submit(d), {
                            close() {
                                close("save");
                            },
                            done
                        });
                    } else {
                        done();
                    }
                } else {
                    // 切换到对应的选项卡
                    Tabs.toGroup({
                        refs,
                        config,
                        prop: keys(error)[0]
                    });
                }
            });
        }
        // 打开表单
        function open(options?: ClForm.Options, plugins?: ClForm.Plugin[]) {
            if (!options) {
                return console.error("Options is not null");
            }
            // 清空
            if (options.isReset !== false) {
                clear();
            }
            // 显示对话框
            visible.value = true;
            // 默认关闭方式
            closeAction = "close";
            // 合并配置
            for (const i in config) {
                switch (i) {
                    // 表单项
                    case "items":
                        function deep(arr: any[]): any[] {
                            return arr.map((e) => {
                                const d = getValue(e);
                                return {
                                    ...d,
                                    children: d?.children ? deep(d.children) : undefined
                                };
                            });
                        }
                        config.items = deep(options.items || []);
                        break;
                    // 事件、参数、操作
                    case "on":
                    case "op":
                    case "props":
                    case "dialog":
                    case "_data":
                        merge(config[i], options[i] || {});
                        break;
                    // 其他
                    default:
                        config[i] = options[i];
                        break;
                }
            }
            // 预设表单值
            if (options?.form) {
                for (const i in options.form) {
                    form[i] = options.form[i];
                }
            }
            // 设置表单数据
            config.items.forEach((e) => {
                function deep(e: ClForm.Item) {
                    if (e.prop) {
                        // 解析 prop
                        if (e.prop.includes(".")) {
                            e.prop = e.prop.replace(/\./g, "-");
                        }
                        // prop 合并
                        Tabs.mergeProp(e);
                        // hook 绑定值
                        formHook.bind({
                            ...e,
                            value: form[e.prop] !== undefined ? form[e.prop] : cloneDeep(e.value),
                            form
                        });
                        // 表单验证
                        if (e.required) {
                            e.rules = {
                                required: true,
                                message: dict.label.nonEmpty.replace("{label}", e.label || "")
                            };
                        }
                    }
                    // 设置 tabs 默认值
                    if (e.type == "tabs") {
                        Tabs.set(e.value);
                    }
                    // 子集
                    if (e.children) {
                        e.children.forEach(deep);
                    }
                }
                deep(e);
            });
            // 设置默认值
            if (!defForm) {
                defForm = cloneDeep(form);
            }
            // 创建插件
            plugin.create(plugins);
            // 打开回调
            nextTick(() => {
                setTimeout(() => {
                    // 打开事件
                    if (config.on?.open) {
                        config.on.open(form);
                    }
                }, 10);
            });
        }
        // 绑定表单数据
        function bindForm(data: any) {
            config.items.forEach((e) => {
                function deep(e: ClForm.Item) {
                    formHook.bind({
                        ...e,
                        value: e.prop ? data[e.prop] : undefined,
                        form: data
                    });
                    if (e.children) {
                        e.children.forEach(deep);
                    }
                }
                deep(e);
            });
            assign(form, data);
        }
        // 渲染表单项
        function renderFormItem(e: ClForm.Item) {
            const { isDisabled } = config._data;
            if (e.type == "tabs") {
                return (
                    <cl-form-tabs v-model={Tabs.active.value} {...e.props} onChange={Tabs.onLoad} />
                );
            }
            // 是否隐藏
            e._hidden = parseHidden(e.hidden, {
                scope: form
            });
            // 分组显示
            const inGroup = e.group ? e.group === Tabs.active.value : true;
            // 是否已加载完成
            const isLoaded = e.component && Tabs.isLoaded(e.group);
            // 表单项
            const FormItem = h(
                <el-form-item
                    class={{
                        "no-label": !(e.renderLabel || e.label),
                        "has-children": !!e.children
                    }}
                    key={e.prop}
                    data-group={e.group || "-"}
                    data-prop={e.prop || "-"}
                    label-width={props.inline ? "auto" : ""}
                    label={e.label}
                    prop={e.prop}
                    rules={isDisabled ? null : e.rules}
                    required={e._hidden ? false : e.required}
                    v-show={inGroup && !e._hidden}
                />,
                e.props,
                {
                    label() {
                        if (e.renderLabel) {
                            return renderNode(e.renderLabel, {
                                scope: form,
                                render: "slot",
                                slots
                            });
                        } else {
                            return e.label;
                        }
                    },
                    default() {
                        return (
                            <div>
                                <div class="cl-form-item">
                                    {["prepend", "component", "append"]
                                        .filter((k) => e[k])
                                        .map((name) => {
                                            const children = e.children && (
                                                <div class="cl-form-item__children">
                                                    <el-row gutter={10}>
                                                        {e.children.map(renderFormItem)}
                                                    </el-row>
                                                </div>
                                            );
                                            const Item = renderNode(e[name], {
                                                item: e,
                                                prop: e.prop,
                                                scope: form,
                                                slots,
                                                children,
                                                _data: {
                                                    isDisabled
                                                }
                                            });
                                            return (
                                                <div
                                                    v-show={!e.collapse}
                                                    class={[
                                                        `cl-form-item__${name}`,
                                                        {
                                                            flex1: e.flex !== false
                                                        }
                                                    ]}
                                                    style={e[name].style}>
                                                    {Item}
                                                </div>
                                            );
                                        })}
                                </div>
                                {isBoolean(e.collapse) && (
                                    <div
                                        class="cl-form-item__collapse"
                                        onClick={() => {
                                            Action.collapseItem(e);
                                        }}>
                                        <el-divider content-position="center">
                                            {e.collapse
                                                ? dict.label.seeMore
                                                : dict.label.hideContent}
                                        </el-divider>
                                    </div>
                                )}
                            </div>
                        );
                    }
                }
            );
            let span = e.span || style.form.span;
            if (browser.isMini) {
                span = 24;
            }
            // 是否行内
            const Item = props.inline ? (
                FormItem
            ) : (
                <el-col span={span} {...e.col} v-show={inGroup && !e._hidden}>
                    {FormItem}
                </el-col>
            );
            return isLoaded ? Item : null;
        }
        // 渲染表单
        function renderContainer() {
            // 表单项列表
            const children = config.items.map(renderFormItem);
            // 表单标签位置
            const labelPosition =
                browser.isMini && !props.inline
                    ? "top"
                    : config.props.labelPosition || style.form.labelPosition;
            return (
                <div class="cl-form__container" ref={setRefs("form")}>
                    {h(
                        <el-form
                            ref={Form}
                            size={style.size}
                            label-width={style.form.labelWidth}
                            inline={props.inline}
                            require-asterisk-position="right"
                            disabled={saving.value}
                            scroll-to-error
                            model={form}
                            onSubmit={(e: Event) => {
                                submit();
                                e.preventDefault();
                            }}
                        />,
                        {
                            ...config.props,
                            labelPosition
                        },
                        {
                            default: () => {
                                const items = [
                                    slots.prepend && slots.prepend({ scope: form }),
                                    children,
                                    slots.append && slots.append({ scope: form })
                                ];
                                return (
                                    <div class="cl-form__items" v-loading={loading.value}>
                                        {props.inline ? (
                                            items
                                        ) : (
                                            <el-row gutter={10}>{items}</el-row>
                                        )}
                                    </div>
                                );
                            }
                        }
                    )}
                </div>
            );
        }
        // 渲染表单底部按钮
        function renderFooter() {
            const { hidden, buttons, saveButtonText, closeButtonText, justify } = config.op;
            if (hidden) {
                return null;
            }
            const Btns = buttons?.map((e: any) => {
                switch (e) {
                    case "save":
                        return (
                            <el-button
                                type="success"
                                size={style.size}
                                disabled={loading.value}
                                loading={saving.value}
                                onClick={() => {
                                    submit();
                                }}>
                                {saveButtonText}
                            </el-button>
                        );
                    case "close":
                        return (
                            <el-button
                                size={style.size}
                                onClick={() => {
                                    close("close");
                                }}>
                                {closeButtonText}
                            </el-button>
                        );
                    default:
                        return renderNode(e, {
                            scope: form,
                            slots,
                            custom() {
                                return (
                                    <el-button
                                        type={e.type}
                                        {...e.props}
                                        onClick={() => {
                                            e.onClick({ scope: form });
                                        }}>
                                        {e.label}
                                    </el-button>
                                );
                            }
                        });
                }
            });
            return (
                <div
                    class="cl-form__footer"
                    style={{
                        justifyContent: justify || "flex-end"
                    }}>
                    {Btns}
                </div>
            );
        }
        // Tools
        function parseHidden(value: any, { scope }: any) {
            if (isBoolean(value)) {
                return value;
            } else if (isFunction(value)) {
                return value({ scope });
            }
            return false;
        }
        const ctx = {
            name: props.name,
            refs,
            Form,
            visible,
            saving,
            form,
            config,
            loading,
            disabled,
            open,
            close,
            done,
            clear,
            reset,
            submit,
            invokeData,
            bindForm,
            showLoading,
            hideLoading,
            setDisabled,
            Tabs,
            ...Action,
            ...ElFormApi
        };
        expose(ctx);
        return () => {
            if (props.inner) {
                return (
                    visible.value && (
                        <div class="cl-form">
                            {renderContainer()}
                            {renderFooter()}
                        </div>
                    )
                );
            } else {
                return h(
                    <cl-dialog v-model={visible.value} class="cl-form" />,
                    {
                        title: config.title,
                        height: config.height,
                        width: config.width,
                        ...config.dialog,
                        beforeClose,
                        onClosed,
                        keepAlive: false
                    },
                    {
                        default() {
                            return renderContainer();
                        },
                        footer() {
                            return renderFooter();
                        }
                    }
                );
            }
        };
    }
});
packages/crud/src/components/index.tsx
New file
@@ -0,0 +1,50 @@
import { App } from "vue";
import Crud from "./crud";
import AddBtn from "./add-btn";
import AdvBtn from "./adv/btn";
import AdvSearch from "./adv/search";
import Flex from "./flex1";
import Form from "./form";
import FormTabs from "./form-tabs";
import FormCard from "./form-card";
import MultiDeleteBtn from "./multi-delete-btn";
import Pagination from "./pagination";
import RefreshBtn from "./refresh-btn";
import SearchKey from "./search-key";
import Table from "./table";
import Upsert from "./upsert";
import Dialog from "./dialog";
import Filter from "./filter";
import Search from "./search";
import ErrorMessage from "./error-message";
import Row from "./row";
import ContextMenu from "./context-menu";
export const components: { [key: string]: any } = {
    Crud,
    AddBtn,
    AdvBtn,
    AdvSearch,
    Flex,
    Form,
    FormTabs,
    FormCard,
    MultiDeleteBtn,
    Pagination,
    RefreshBtn,
    SearchKey,
    Table,
    Upsert,
    Dialog,
    Filter,
    Search,
    ErrorMessage,
    Row,
    ContextMenu
};
export function useComponent(app: App) {
    for (const i in components) {
        app.component(components[i].name, components[i]);
    }
}
packages/crud/src/components/multi-delete-btn/index.tsx
New file
@@ -0,0 +1,27 @@
import { defineComponent } from "vue";
import { useConfig, useCore } from "../../hooks";
export default defineComponent({
    name: "cl-multi-delete-btn",
    setup(_, { slots }) {
        const { crud } = useCore();
        const { style } = useConfig();
        return () => {
            return (
                crud.getPermission("delete") && (
                    <el-button
                        type="danger"
                        size={style.size}
                        disabled={crud.selection.length === 0}
                        onClick={() => {
                            crud.rowDelete(...crud.selection);
                        }}>
                        {slots.default?.() || crud.dict.label.multiDelete}
                    </el-button>
                )
            );
        };
    }
});
packages/crud/src/components/pagination/index.tsx
New file
@@ -0,0 +1,90 @@
import { defineComponent, h, onMounted, onUnmounted, ref } from "vue";
import { useBrowser, useConfig, useCore } from "../../hooks";
export default defineComponent({
    name: "cl-pagination",
    setup(_, { expose }) {
        const { crud, mitt } = useCore();
        const { style } = useConfig();
        const browser = useBrowser();
        // 总数
        const total = ref(0);
        // 当前页数
        const currentPage = ref(1);
        // 每页大小
        const pageSize = ref(20);
        // 页数发生变化
        function onCurrentChange(index: number) {
            crud.refresh({
                page: index
            });
        }
        // 条目发生变化
        function onSizeChange(size: number) {
            crud.refresh({
                page: 1,
                size
            });
        }
        // 设置分页信息
        function setPagination(res: obj) {
            if (res) {
                currentPage.value = res.currentPage || res.page || 1;
                pageSize.value = res.pageSize || res.size || 20;
                total.value = res.total || 0;
                crud.params.size = pageSize.value;
            }
        }
        // 数据刷新
        function onRefresh(res: ClCrud.Response["page"]) {
            setPagination(res.pagination);
        }
        // 监听刷新事件
        onMounted(() => {
            mitt.on("crud.refresh", onRefresh);
        });
        // 移除监听事件
        onUnmounted(() => {
            mitt.off("crud.refresh", onRefresh);
        });
        expose({
            total,
            currentPage,
            pageSize,
            setPagination
        });
        return () => {
            return h(
                <el-pagination
                    class="cl-pagination"
                    size={browser.isMini ? 'small' : style.size}
                    background
                    page-sizes={[10, 20, 30, 40, 50, 100]}
                    pager-count={browser.isMini ? 5 : 7}
                    layout={
                        browser.isMini ? "total, pager" : "total, sizes, prev, pager, next, jumper"
                    }
                />,
                {
                    onSizeChange,
                    onCurrentChange,
                    total: total.value,
                    currentPage: currentPage.value,
                    pageSize: pageSize.value
                }
            );
        };
    }
});
packages/crud/src/components/refresh-btn/index.tsx
New file
@@ -0,0 +1,23 @@
import { defineComponent } from "vue";
import { useConfig, useCore } from "../../hooks";
export default defineComponent({
    name: "cl-refresh-btn",
    setup(_, { slots }) {
        const { crud } = useCore();
        const { style } = useConfig();
        return () => {
            return (
                <el-button
                    size={style.size}
                    onClick={() => {
                        crud.refresh();
                    }}>
                    {slots.default?.() || crud.dict.label.refresh}
                </el-button>
            );
        };
    }
});
packages/crud/src/components/row/index.tsx
New file
@@ -0,0 +1,11 @@
import { defineComponent } from "vue";
export default defineComponent({
    name: "cl-row",
    setup(_, { slots }) {
        return () => {
            return <el-row class="cl-row">{slots.default && slots.default()}</el-row>;
        };
    }
});
packages/crud/src/components/search-key/index.tsx
New file
@@ -0,0 +1,178 @@
import { defineComponent, ref, computed, type PropType, useModel } from "vue";
import { useConfig, useCore } from "../../hooks";
import { parsePx } from "../../utils";
import { debounce } from "lodash-es";
export default defineComponent({
    name: "cl-search-key",
    props: {
        // 绑定值
        modelValue: String,
        // 选中字段
        field: {
            type: String,
            default: "keyWord"
        },
        // 字段列表
        fieldList: {
            type: Array as PropType<Array<{ label: string; value: string }>>,
            default: () => []
        },
        // 搜索时的钩子
        onSearch: Function,
        // 输入框占位内容
        placeholder: String,
        // 宽度
        width: {
            type: [String, Number],
            default: 280
        },
        // 是否实时刷新
        refreshOnInput: Boolean
    },
    emits: ["update:modelValue", "change", "field-change"],
    setup(props, { emit, expose }) {
        const { crud } = useCore();
        const { style } = useConfig();
        // 选中字段
        const selectField = ref(props.field);
        // 加载状态
        const loading = ref(false);
        // 文字提示
        const placeholder = computed(() => {
            if (props.placeholder) {
                return props.placeholder;
            } else {
                const item = props.fieldList.find((e) => e.value == selectField.value);
                if (item) {
                    return crud.dict.label.placeholder + item.label;
                } else {
                    return crud.dict.label.searchKey;
                }
            }
        });
        // 搜索内容
        const value = useModel(props, "modelValue");
        // 锁
        let lock = false;
        // 搜索
        function search() {
            if (!lock) {
                const params: obj = {};
                props.fieldList.forEach((e) => {
                    params[e.value] = undefined;
                });
                async function next(newParams?: obj) {
                    loading.value = true;
                    await crud.refresh({
                        page: 1,
                        ...params,
                        [selectField.value]: value.value || undefined,
                        ...newParams
                    })
                        .catch(err => {
                            console.error(err);
                        })
                    loading.value = false;
                }
                if (props.onSearch) {
                    props.onSearch(params, { next });
                } else {
                    next();
                }
            }
        }
        // 回车搜索
        function onKeydown({ key }: KeyboardEvent) {
            if (key === "Enter") {
                search();
            }
        }
        // 监听变化
        function onChange(val: string) {
            if (!props.refreshOnInput) {
                search();
                lock = true;
                setTimeout(() => {
                    lock = false;
                }, 300);
                emit("change", val);
            }
        }
        // 监听输入
        const onInput = debounce((val: string) => {
            emit("change", val);
            if (props.refreshOnInput) {
                search();
            }
        }, 300);
        // 监听字段选择
        function onFieldChange() {
            emit("field-change", selectField.value);
            value.value = undefined;
        }
        expose({
            search
        });
        return () => {
            return (
                <div class="cl-search-key">
                    <el-select
                        class="cl-search-key__select"
                        size={style.size}
                        v-model={selectField.value}
                        v-show={props.fieldList.length > 0}
                        onChange={onFieldChange}>
                        {props.fieldList.map((e, i) => (
                            <el-option key={i} label={e.label} value={e.value} />
                        ))}
                    </el-select>
                    <div class="cl-search-key__wrap" style={{ width: parsePx(props.width) }}>
                        <el-input
                            v-model={value.value}
                            size={style.size}
                            placeholder={placeholder.value}
                            onKeydown={onKeydown}
                            onChange={onChange}
                            onInput={onInput}
                            clearable
                        />
                        <el-button
                            size={style.size}
                            type="primary"
                            loading={loading.value}
                            onClick={search}>
                            {crud.dict.label.search}
                        </el-button>
                    </div>
                </div>
            );
        };
    }
});
packages/crud/src/components/search/helper/plugins.ts
New file
@@ -0,0 +1,21 @@
import { getCurrentInstance } from "vue";
import { useConfig } from "../../../hooks";
import { uniqueFns } from "../../../utils";
export function usePlugins() {
    const that: any = getCurrentInstance();
    const { style } = useConfig();
    // 插件创建
    function create(plugins: ClSearch.Plugin[] = []) {
        uniqueFns([...(style.search?.plugins || []), ...plugins]).forEach((p) => {
            p({
                exposed: that.exposed
            });
        });
    }
    return {
        create
    };
}
packages/crud/src/components/search/index.tsx
New file
@@ -0,0 +1,298 @@
import { useConfig, useCore, useForm, useProxy, useRefs } from "../../hooks";
import {
    onMounted,
    PropType,
    defineComponent,
    ref,
    h,
    reactive,
    inject,
    mergeProps,
    nextTick,
    onUnmounted
} from "vue";
import { useApi } from "../form/helper";
import { Search, Refresh, Bottom, Top } from "@element-plus/icons-vue";
import { mitt } from "../../utils/mitt";
import { isArray, isEmpty } from "lodash-es";
import { usePlugins } from "./helper/plugins";
export default defineComponent({
    name: "cl-search",
    props: {
        // 是否行内
        inline: {
            type: Boolean,
            default: true
        },
        // cl-form 表单配置
        props: {
            type: Object,
            default: () => ({})
        },
        // 表单值
        data: {
            type: Object,
            default: () => ({})
        },
        // 列
        items: {
            type: Array as PropType<ClForm.Item[]>,
            default: () => []
        },
        // 是否需要重置按钮
        resetBtn: {
            type: Boolean,
            default: false
        },
        // 是否需要折叠
        collapse: {
            type: Boolean,
            default: false
        },
        // 初始化
        onLoad: Function,
        // 搜索时钩子
        onSearch: Function
    },
    emits: ["reset"],
    setup(props, { slots, expose, emit }) {
        const { crud } = useCore();
        const { refs, setRefs } = useRefs()
        const { style } = useConfig();
        const plugin = usePlugins()
        // 配置
        const config = reactive<ClSearch.Config>(
            mergeProps(props, inject("useSearch__options") || {})
        );
        // cl-form
        const Form = useForm();
        // 加载中
        const loading = ref(false);
        // 展开
        const isExpand = ref(!config.collapse);
        // 显示展开、收起按钮
        const showExpandBtn = ref(false);
        // 搜索
        function search(params?: any) {
            const form = Form.value?.getForm();
            async function next(data?: any) {
                loading.value = true;
                const d = {
                    page: 1,
                    ...form,
                    ...data,
                    ...params
                };
                for (const i in d) {
                    if (d[i] === "") {
                        d[i] = undefined;
                    }
                }
                const res = await crud.refresh(d);
                loading.value = false;
                return res;
            }
            if (config.onSearch) {
                config.onSearch(form, { next });
            } else {
                next();
            }
        }
        // 重置
        function reset() {
            const d: any = {};
            config.items?.map((e) => {
                if (typeof e.hook != 'string' && e.hook?.reset) {
                    const props = e.hook.reset(e.prop!)
                    if (isArray(props)) {
                        props.forEach((prop) => {
                            d[prop] = undefined;
                        })
                    }
                }
                d[e.prop!] = undefined;
            });
            // 重置表单
            Form.value?.reset();
            // 列表刷新
            search(d);
            // 重置事件
            emit("reset", d);
        }
        // 收起、展开
        function expand() {
            isExpand.value = !isExpand.value;
            nextTick(() => {
                crud?.["cl-table"].calcMaxHeight()
            })
        }
        // 判断展开状态
        function onExpand() {
            if (config.collapse) {
                const el = refs.form?.querySelector(".cl-form__items");
                if (el) {
                    showExpandBtn.value = el.clientHeight > 84;
                }
            }
        }
        function onResize() {
            onExpand();
        }
        const ctx = {
            search,
            reset,
            Form,
            config,
            ...useApi({ Form })
        };
        useProxy(ctx);
        expose(ctx);
        plugin.create(config.plugins);
        onMounted(() => {
            Form.value?.open({
                op: {
                    hidden: true
                },
                props: {
                    labelPosition: 'right',
                    ...config.props
                },
                items: config.items?.map(e => {
                    return {
                        col: {
                            sm: 12,
                            md: 8,
                            xs: 24,
                            lg: 6,
                        },
                        ...e,
                    }
                }),
                form: config.data,
                on: {
                    open(data) {
                        config.onLoad?.(data);
                        onExpand();
                    },
                    change(data, prop) {
                        config.onChange?.(data, prop);
                    }
                }
            });
            mitt.on("resize", onResize);
        });
        onUnmounted(() => {
            mitt.off("resize", onResize);
        })
        return () => {
            const btnEl = (
                <el-form-item label=" " class="cl-search__btns">
                    {/* 重置按钮 */}
                    {config.resetBtn && (
                        <el-button size={style.size} icon={Refresh} onClick={reset}>
                            {crud.dict.label.reset}
                        </el-button>
                    )}
                    {/* 搜索按钮 */}
                    <el-button
                        type="primary"
                        loading={loading.value}
                        size={style.size}
                        icon={Search}
                        onClick={() => {
                            search();
                        }}>
                        {crud.dict.label.search}
                    </el-button>
                    {/* 自定义按钮 */}
                    {slots?.buttons?.(Form.value?.form)}
                </el-form-item>
            );
            return (
                <div class={["cl-search", isExpand.value ? "is-expand" : "is-fold", {
                    'is-inline': config.inline,
                    'is-collapse': config.collapse,
                }]}>
                    <div class="cl-search__form" ref={setRefs("form")}>
                        {h(
                            <cl-form
                                ref={Form}
                                inner
                                inline={config.inline}
                                enable-plugin={false}
                                name='search'
                            />,
                            {},
                            {
                                append() {
                                    return config.collapse ? null : (isEmpty(config.items) || btnEl);
                                },
                                ...slots
                            }
                        )}
                    </div>
                    {config.collapse && (
                        <div class="cl-search__more">
                            {showExpandBtn.value && (
                                <el-button onClick={expand}>
                                    <span>{isExpand.value ? crud.dict.label.collapse : crud.dict.label.expand}</span>
                                    <el-icon>{isExpand.value ? <Top /> : <Bottom />}</el-icon>
                                </el-button>
                            )}
                            <cl-flex1 />
                            {btnEl}
                        </div>
                    )}
                </div>
            );
        };
    }
});
packages/crud/src/components/table/helper/data.ts
New file
@@ -0,0 +1,35 @@
import { nextTick, ref } from "vue";
import { useCore } from "../../../hooks";
export function useData({ config, Table }: { config: ClTable.Config; Table: Vue.Ref<any> }) {
    const { mitt, crud } = useCore();
    // 列表数据
    const data = ref<obj[]>([]);
    // 设置数据
    function setData(list: obj[]) {
        data.value = list;
    }
    // 监听刷新
    mitt.on("crud.refresh", ({ list }: ClCrud.Response["page"]) => {
        data.value = list;
        // 显示选中行
        nextTick(() => {
            crud.selection.forEach((e) => {
                const d = list.find((a) => a[config.rowKey] == e[config.rowKey]);
                if (d) {
                    Table.value.toggleRowSelection(d, true);
                }
            });
        });
    });
    return {
        data,
        setData
    };
}
packages/crud/src/components/table/helper/header.tsx
New file
@@ -0,0 +1,91 @@
import { CloseBold, Search } from "@element-plus/icons-vue";
import { h } from "vue";
import { useCrud } from "../../../hooks";
import { renderNode } from "../../../utils/vnode";
export function renderHeader(item: ClTable.Column, { scope, slots }: any) {
    const crud = useCrud();
    const slot = slots[`header-${item.prop}`];
    if (slot) {
        return slot({
            scope
        });
    }
    if (!item.search || !item.search.component) {
        return item.label;
    }
    // 显示输入框
    function show(e: MouseEvent) {
        item.search.isInput = true;
        e.stopPropagation();
    }
    // 隐藏输入框
    function hide() {
        if (item.search.value !== undefined) {
            item.search.value = undefined;
            refresh();
        }
        item.search.isInput = false;
    }
    // 刷新
    function refresh(params?: any) {
        const { value } = item.search;
        crud.value?.refresh({
            page: 1,
            [item.prop]: value === "" ? undefined : value,
            ...params
        });
    }
    // 文字
    const text = (
        <div class="cl-table-header__search-label" onClick={show}>
            <el-icon size={14}>{item.search.icon?.() ?? <Search />}</el-icon>
            {item.renderLabel ? item.renderLabel(scope) : item.label}
        </div>
    );
    // 输入框
    const input = h(renderNode(item.search.component, { prop: item.prop }), {
        clearable: true,
        modelValue: item.search.value,
        onVnodeMounted(vn) {
            // 默认聚焦
            vn.component?.exposed?.focus?.();
        },
        onInput(val: any) {
            item.search.value = val;
        },
        onChange(val: any) {
            item.search.value = val;
            // 更改时刷新列表
            if (item.search.refreshOnChange) {
                refresh();
            }
        }
    });
    return (
        <div class={["cl-table-header__search", { "is-input": item.search.isInput }]}>
            <div class="cl-table-header__search-inner">{item.search.isInput ? input : text}</div>
            {item.search.isInput && (
                <div class="cl-table-header__search-close" onClick={hide}>
                    <el-icon>
                        <CloseBold />
                    </el-icon>
                </div>
            )}
        </div>
    );
}
packages/crud/src/components/table/helper/height.ts
New file
@@ -0,0 +1,99 @@
import { debounce, last } from "lodash-es";
import { nextTick, onActivated, onMounted, ref } from "vue";
import { addClass, removeClass } from "../../../utils";
import { mitt } from "../../../utils/mitt";
// 表格高度
export function useHeight({ config, Table }: { Table: Vue.Ref<any>; config: ClTable.Config }) {
    // 最大高度
    const maxHeight = ref(0);
    // 计算表格最大高度
    const update = debounce(async () => {
        await nextTick();
        let vm = Table.value;
        if (vm) {
            while (!vm.$parent?.$el.className?.includes("cl-crud")) {
                vm = vm.$parent;
            }
            if (vm) {
                const p = vm.$parent.$el;
                await nextTick();
                // 高度
                let h = 0;
                // 表格下间距
                if (vm.$el.className.includes("cl-row")) {
                    h += 10;
                }
                // 上高度
                h += vm.$el.offsetTop;
                // 获取下高度
                let n = vm.$el.nextSibling;
                // 集合
                const arr = [vm.$el];
                while (n) {
                    if (n.offsetHeight > 0) {
                        h += n.offsetHeight || 0;
                        arr.push(n);
                        if (n.className.includes("cl-row--last")) {
                            h += 10;
                        }
                    }
                    n = n.nextSibling;
                }
                // 移除 cl-row--last
                arr.forEach((e) => {
                    removeClass(e, "cl-row--last");
                });
                // 最后一个可视元素
                const z = last(arr);
                // 去掉 cl-row 下间距高度
                if (z?.className.includes("cl-row")) {
                    addClass(z, "cl-row--last");
                    h -= 10;
                }
                // 上间距
                h += parseInt(window.getComputedStyle(p).paddingTop, 10);
                // 设置最大高度
                if (config.autoHeight) {
                    maxHeight.value = p.clientHeight - h;
                }
            }
        }
    }, 100);
    // 窗口大小改变事件
    mitt.on("resize", () => {
        update();
    });
    onMounted(function () {
        update();
    });
    onActivated(function () {
        update();
    });
    return {
        maxHeight,
        calcMaxHeight: update
    };
}
packages/crud/src/components/table/helper/index.ts
New file
@@ -0,0 +1,43 @@
import { inject, reactive, ref } from "vue";
import { useConfig } from "../../../hooks";
import { getValue, mergeConfig } from "../../../utils";
import type { TableInstance } from "element-plus";
export function useTable(props: any) {
    const { style } = useConfig();
    const Table = ref<TableInstance>();
    // 配置
    const config = reactive<ClTable.Config>(mergeConfig(props, inject("useTable__options") || {}));
    // 列表项动态处理
    config.columns = (config.columns || []).map((e) => getValue(e));
    // 自动高度
    config.autoHeight = config.autoHeight ?? style.table.autoHeight;
    // 右键菜单
    config.contextMenu = config.contextMenu ?? style.table.contextMenu;
    // 事件
    if (!config.on) {
        config.on = {};
    }
    // 参数
    if (!config.props) {
        config.props = {};
    }
    return { Table, config };
}
export * from "./data";
export * from "./height";
export * from "./op";
export * from "./render";
export * from "./row";
export * from "./selection";
export * from "./sort";
export * from "./header";
packages/crud/src/components/table/helper/op.ts
New file
@@ -0,0 +1,69 @@
import { nextTick, ref } from "vue";
import { useCore } from "../../../hooks";
import { isArray, isBoolean } from "lodash-es";
export function useOp({ config }: { config: ClTable.Config }) {
    const { mitt } = useCore();
    // 是否可见,用于解决一些显示隐藏的副作用
    const visible = ref(true);
    // 重新构建
    async function reBuild(cb?: fn) {
        visible.value = false;
        await nextTick();
        if (cb) {
            cb();
        }
        visible.value = true;
        await nextTick();
        mitt.emit("resize");
    }
    // 显示列
    function showColumn(prop: string | string[], status?: boolean) {
        const keys = isArray(prop) ? prop : [prop];
        // 多级表头
        function deep(list: ClTable.Column[]) {
            list.forEach((e) => {
                if (e.prop && keys.includes(e.prop)) {
                    e.hidden = isBoolean(status) ? !status : false;
                }
                if (e.children) {
                    deep(e.children);
                }
            });
        }
        deep(config.columns);
    }
    // 隐藏列
    function hideColumn(prop: string | string[]) {
        showColumn(prop, false);
    }
    // 设置列
    function setColumns(list: ClTable.Column[]) {
        if (list) {
            reBuild(() => {
                config.columns.splice(0, config.columns.length, ...list);
            });
        }
    }
    return {
        visible,
        reBuild,
        showColumn,
        hideColumn,
        setColumns
    };
}
packages/crud/src/components/table/helper/plugins.ts
New file
@@ -0,0 +1,22 @@
import { getCurrentInstance } from "vue";
import { useConfig } from "../../../hooks";
import { uniqueFns } from "../../../utils";
export function usePlugins() {
    const that: any = getCurrentInstance();
    const { style } = useConfig();
    // 插件创建
    function create(plugins: ClTable.Plugin[] = []) {
        // 执行
        uniqueFns([...(style.table.plugins || []), ...plugins]).forEach((p) => {
            p({
                exposed: that.exposed
            });
        });
    }
    return {
        create
    };
}
packages/crud/src/components/table/helper/render.tsx
New file
@@ -0,0 +1,327 @@
import { h, useSlots } from "vue";
import { useCore, useBrowser, useConfig } from "../../../hooks";
import { assign, cloneDeep, isArray, isEmpty, isObject, isString, orderBy } from "lodash-es";
import { deepFind, getValue } from "../../../utils";
import { renderNode } from "../../../utils/vnode";
import { renderHeader } from "./header";
// 渲染
export function useRender() {
    const browser = useBrowser();
    const slots = useSlots();
    const { crud } = useCore();
    const { style } = useConfig();
    // 渲染列
    function renderColumn(columns: ClTable.Column[]) {
        const arr = columns.map((e) => {
            const d = getValue(e);
            if (!d.orderNum) {
                d.orderNum = 0;
            }
            return d;
        });
        return orderBy(arr, "orderNum", "asc")
            .map((item, index) => {
                if (item.hidden) {
                    return null;
                }
                const ElTableColumn = (
                    <el-table-column
                        key={`cl-table-column__${index}`}
                        align={style.table.column.align}
                        header-align={style.table.column.headerAlign}
                        minWidth={style.table.column.minWidth}
                    />
                );
                // 操作按钮
                if (item.type === "op") {
                    const props = assign(
                        {
                            label: crud.dict.label.op,
                            width: style.table.column.opWidth,
                            fixed: browser.isMini ? null : "right"
                        },
                        item
                    );
                    return h(ElTableColumn, props, {
                        default: (scope: any) => {
                            return (
                                <div class="cl-table__op">
                                    {renderOpButtons(item.buttons, { scope })}
                                </div>
                            );
                        }
                    });
                }
                // 多选,序号
                else if (["selection", "index"].includes(item.type)) {
                    return h(ElTableColumn, item);
                }
                // 默认
                else {
                    function deep(item: ClTable.Column) {
                        if (item.hidden) {
                            return null;
                        }
                        const props: obj = cloneDeep(item);
                        // Cannot set property children of #<Element>
                        delete props.children;
                        return h(ElTableColumn, props, {
                            header(scope: any) {
                                return renderHeader(item, { scope, slots });
                            },
                            default(scope: any) {
                                if (item.children) {
                                    return item.children.map(deep);
                                }
                                // 使用插槽
                                const slot = slots[`column-${item.prop}`];
                                if (slot) {
                                    return slot({
                                        scope,
                                        item
                                    });
                                } else {
                                    // 绑定值
                                    let value = scope.row[item.prop];
                                    // 格式化
                                    if (item.formatter) {
                                        value = item.formatter(
                                            scope.row,
                                            scope.column,
                                            value,
                                            scope.$index
                                        );
                                        if (isObject(value)) {
                                            return value;
                                        }
                                    }
                                    // 自定义渲染
                                    if (item.render) {
                                        return item.render(
                                            scope.row,
                                            scope.column,
                                            value,
                                            scope.$index
                                        );
                                    }
                                    // 自定义渲染2
                                    else if (item.component) {
                                        return renderNode(item.component, {
                                            prop: item.prop,
                                            scope: scope.row,
                                            _data: {
                                                column: scope.column,
                                                index: scope.$index,
                                                row: scope.row
                                            }
                                        });
                                    }
                                    // 字典状态
                                    else if (item.dict) {
                                        return renderDict(value, item);
                                    }
                                    // 空数据
                                    else if (isEmpty(value)) {
                                        return scope.emptyText;
                                    } else {
                                        return value;
                                    }
                                }
                            }
                        });
                    }
                    return deep(item);
                }
            })
            .filter(Boolean);
    }
    // 渲染操作按钮
    function renderOpButtons(buttons: any, { scope }: any) {
        const list = getValue(buttons || ["edit", "delete"], { scope }) as ClTable.OpButton;
        return list.map((vnode) => {
            if (vnode === "info") {
                return (
                    <el-button
                        plain
                        size={style.size}
                        v-show={crud.getPermission("info")}
                        onClick={(e: MouseEvent) => {
                            crud.rowInfo(scope.row);
                            e.stopPropagation();
                        }}>
                        {crud.dict.label?.info}
                    </el-button>
                );
            } else if (vnode === "edit") {
                return (
                    <el-button
                        text
                        type="primary"
                        size={style.size}
                        v-show={crud.getPermission("update")}
                        onClick={(e: MouseEvent) => {
                            crud.rowEdit(scope.row);
                            e.stopPropagation();
                        }}>
                        {crud.dict.label?.update}
                    </el-button>
                );
            } else if (vnode === "delete") {
                return (
                    <el-button
                        text
                        type="danger"
                        size={style.size}
                        v-show={crud.getPermission("delete")}
                        onClick={(e: MouseEvent) => {
                            crud.rowDelete(scope.row);
                            e.stopPropagation();
                        }}>
                        {crud.dict.label?.delete}
                    </el-button>
                );
            } else {
                if (typeof vnode === "object") {
                    if (vnode.hidden) {
                        return null;
                    }
                }
                return renderNode(vnode, {
                    scope,
                    slots,
                    custom(vnode) {
                        return (
                            <el-button
                                text
                                type={vnode.type}
                                {...vnode?.props}
                                onClick={(e: MouseEvent) => {
                                    vnode.onClick({ scope });
                                    e.stopPropagation();
                                }}>
                                {vnode.label}
                            </el-button>
                        );
                    }
                });
            }
        });
    }
    // 渲染字典
    function renderDict(value: any, item: ClTable.Column) {
        // 选项列表
        const list = cloneDeep(item.dict || []) as DictOptions;
        // 字符串分隔符
        const separator = item.dictSeparator === undefined ? "," : item.dictSeparator;
        // 设置颜色
        if (item.dictColor) {
            list.forEach((e, i) => {
                if (!e.color) {
                    e.color = style.colors[i];
                }
            });
        }
        // 绑定值
        let values: any[] = [];
        // 格式化值
        if (isArray(value)) {
            values = value;
        } else if (isString(value)) {
            if (separator) {
                values = value.split(separator);
            } else {
                values = [value];
            }
        } else {
            values = [value];
        }
        // 返回值
        const result = values
            .filter((e) => e !== undefined && e !== null && e !== "")
            .map((v) => {
                const d = deepFind(v, list, { allLevels: item.dictAllLevels }) || {
                    label: v,
                    value: v
                };
                return {
                    ...d,
                    children: []
                };
            });
        // 格式化返回
        if (item.dictFormatter) {
            return item.dictFormatter(result);
        } else {
            // tag 返回
            return result.map((e) => {
                return h(
                    <el-tag disable-transitions style="margin: 2px; border: 0" />,
                    {
                        type: e.type,
                        closable: e.closable,
                        hit: e.hit,
                        color: e.color,
                        size: e.size,
                        effect: e.effect || "dark",
                        round: e.round
                    },
                    {
                        default: () => <span>{e.label}</span>
                    }
                );
            });
        }
    }
    // 插槽 empty
    function renderEmpty(emptyText: string) {
        return (
            <div class="cl-table__empty">
                {slots.empty ? (
                    slots.empty()
                ) : (
                    <el-empty image-size={100} description={emptyText}></el-empty>
                )}
            </div>
        );
    }
    // 插槽 append
    function renderAppend() {
        return <div class="cl-table__append">{slots.append && slots.append()}</div>;
    }
    return {
        renderColumn,
        renderEmpty,
        renderAppend
    };
}
packages/crud/src/components/table/helper/row.ts
New file
@@ -0,0 +1,130 @@
import { isEmpty, isFunction } from "lodash-es";
import { useCore } from "../../../hooks";
import { ContextMenu } from "../../context-menu";
// 单元行事件
export function useRow({
    Table,
    config,
    Sort
}: {
    Table: Vue.Ref<any>;
    config: ClTable.Config;
    Sort: {
        defaultSort: {
            prop?: string;
            order?: string;
        };
        changeSort(prop: string, order: string): void;
    };
}) {
    const { crud } = useCore();
    // 右键菜单
    function onRowContextMenu(row: obj, column: obj, event: PointerEvent) {
        // 菜单按钮
        const buttons = config.contextMenu;
        // 是否开启
        const enable = !isEmpty(buttons);
        if (enable) {
            // 高亮
            Table.value.setCurrentRow(row);
            // 解析按钮
            const list = buttons
                .map((e) => {
                    switch (e) {
                        case "refresh":
                            return {
                                label: crud.dict.label.refresh,
                                callback(done: fn) {
                                    crud.refresh();
                                    done();
                                }
                            };
                        case "edit":
                        case "update":
                            return {
                                label: crud.dict.label.update,
                                hidden: !crud.getPermission("update"),
                                callback(done: fn) {
                                    crud.rowEdit(row);
                                    done();
                                }
                            };
                        case "delete":
                            return {
                                label: crud.dict.label.delete,
                                hidden: !crud.getPermission("delete"),
                                callback(done: fn) {
                                    crud.rowDelete(row);
                                    done();
                                }
                            };
                        case "info":
                            return {
                                label: crud.dict.label.info,
                                hidden: !crud.getPermission("info"),
                                callback(done: fn) {
                                    crud.rowInfo(row);
                                    done();
                                }
                            };
                        case "check":
                            return {
                                label: crud.selection.find((e) => e.id == row.id)
                                    ? crud.dict.label.deselect
                                    : crud.dict.label.select,
                                hidden: !config.columns.find((e) => e.type === "selection"),
                                callback(done: fn) {
                                    Table.value.toggleRowSelection(row);
                                    done();
                                }
                            };
                        case "order-desc":
                            return {
                                label: `${column.label} - ${crud.dict.label.desc}`,
                                hidden: !column.sortable,
                                callback(done: fn) {
                                    Sort.changeSort(column.property, "desc");
                                    done();
                                }
                            };
                        case "order-asc":
                            return {
                                label: `${column.label} - ${crud.dict.label.asc}`,
                                hidden: !column.sortable,
                                callback(done: fn) {
                                    Sort.changeSort(column.property, "asc");
                                    done();
                                }
                            };
                        default:
                            if (isFunction(e)) {
                                return e(row, column, event);
                            } else {
                                return e;
                            }
                    }
                })
                .filter((e) => Boolean(e) && !e.hidden);
            // 打开菜单
            if (!isEmpty(list)) {
                ContextMenu.open(event, {
                    list
                });
            }
        }
        // 回调
        if (config.onRowContextmenu) {
            config.onRowContextmenu(row, column, event);
        }
    }
    return {
        onRowContextMenu
    };
}
packages/crud/src/components/table/helper/selection.ts
New file
@@ -0,0 +1,16 @@
import { useCore } from "../../../hooks";
export function useSelection({ emit }: { emit: Vue.Emit }) {
    const { crud } = useCore();
    // 选择项发生变化
    function onSelectionChange(selection: any[]) {
        crud.selection.splice(0, crud.selection.length, ...selection);
        emit("selection-change", crud.selection);
    }
    return {
        selection: crud.selection,
        onSelectionChange
    };
}
packages/crud/src/components/table/helper/sort.ts
New file
@@ -0,0 +1,86 @@
import { useCore } from "../../../hooks";
// 排序
export function useSort({
    config,
    Table,
    emit
}: {
    config: ClTable.Config;
    Table: Vue.Ref<any>;
    emit: Vue.Emit;
}) {
    const { crud } = useCore();
    // 设置默认排序Ï
    const defaultSort = (function () {
        let { prop, order } = config.defaultSort || {};
        const item = config.columns.find((e) =>
            ["desc", "asc", "descending", "ascending"].find((a) => a == e.sortable)
        );
        if (item) {
            prop = item.prop;
            order = ["descending", "desc"].find((a) => a == item.sortable)
                ? "descending"
                : "ascending";
        }
        if (order && prop) {
            crud.params.order = ["descending", "desc"].includes(order) ? "desc" : "asc";
            crud.params.prop = prop;
            return {
                prop,
                order
            };
        }
        return {};
    })();
    // 排序监听
    function onSortChange({ prop, order }: { prop: string | undefined; order: string }) {
        if (config.sortRefresh) {
            if (order === "descending") {
                order = "desc";
            }
            if (order === "ascending") {
                order = "asc";
            }
            if (!order) {
                prop = undefined;
            }
            crud.refresh({
                prop,
                order,
                page: 1
            });
        }
        emit("sort-change", { prop, order });
    }
    // 改变排序
    function changeSort(prop: string, order: string) {
        if (order === "desc") {
            order = "descending";
        }
        if (order === "asc") {
            order = "ascending";
        }
        Table.value?.sort(prop, order);
    }
    return {
        defaultSort,
        onSortChange,
        changeSort
    };
}
packages/crud/src/components/table/index.tsx
New file
@@ -0,0 +1,165 @@
import { defineComponent, h } from "vue";
import {
    useRow,
    useHeight,
    useRender,
    useSort,
    useData,
    useSelection,
    useOp,
    useTable
} from "./helper";
import { useCore, useProxy, useElApi, useConfig } from "../../hooks";
import { usePlugins } from "./helper/plugins";
export default defineComponent({
    name: "cl-table",
    props: {
        // 列配置
        columns: {
            type: Array,
            default: () => []
        },
        // 是否自动计算高度
        autoHeight: {
            type: Boolean,
            default: null
        },
        // 固定高度
        height: null,
        // 右键菜单
        contextMenu: {
            type: [Array, Boolean],
            default: null
        },
        // 默认排序
        defaultSort: Object,
        // 排序后是否刷新
        sortRefresh: {
            type: Boolean,
            default: true
        },
        // 空数据显示文案
        emptyText: String,
        // 当前行的 key
        rowKey: {
            type: String,
            default: "id"
        }
    },
    emits: ["selection-change", "sort-change"],
    setup(props, { emit, expose }) {
        const { crud } = useCore();
        const { style } = useConfig();
        const { Table, config } = useTable(props);
        const plugin = usePlugins();
        // 排序
        const Sort = useSort({ config, emit, Table });
        // 行
        const Row = useRow({
            config,
            Table,
            Sort
        });
        // 高度
        const Height = useHeight({ config, Table });
        // 数据
        const Data = useData({ config, Table });
        // 多选
        const Selection = useSelection({ emit });
        // 操作
        const Op = useOp({ config });
        // 方法
        const ElTableApi = useElApi(
            [
                "clearSelection",
                "getSelectionRows",
                "toggleRowSelection",
                "toggleAllSelection",
                "toggleRowExpansion",
                "setCurrentRow",
                "clearSort",
                "clearFilter",
                "doLayout",
                "sort",
                "scrollTo",
                "setScrollTop",
                "setScrollLeft",
                "updateKeyChildren"
            ],
            Table
        );
        const ctx = {
            Table,
            config,
            columns: config.columns,
            ...Selection,
            ...Data,
            ...Sort,
            ...Row,
            ...Height,
            ...Op,
            ...ElTableApi
        };
        useProxy(ctx);
        expose(ctx);
        plugin.create(config.plugins);
        return () => {
            const { renderColumn, renderAppend, renderEmpty } = useRender();
            return (
                ctx.visible.value &&
                h(
                    <el-table class="cl-table" ref={Table} v-loading={crud.loading} />,
                    {
                        ...config.on,
                        ...config.props,
                        // config
                        maxHeight: config.autoHeight ? ctx.maxHeight.value : null,
                        height: config.autoHeight ? config.height : null,
                        rowKey: config.rowKey,
                        // ctx
                        defaultSort: ctx.defaultSort,
                        data: ctx.data.value,
                        onRowContextmenu: ctx.onRowContextMenu,
                        onSelectionChange: ctx.onSelectionChange,
                        onSortChange: ctx.onSortChange,
                        // style
                        size: style.size,
                        border: style.table.border,
                        highlightCurrentRow: style.table.highlightCurrentRow,
                        resizable: style.table.resizable,
                        stripe: style.table.stripe,
                    },
                    {
                        default() {
                            return renderColumn(ctx.columns);
                        },
                        empty() {
                            return renderEmpty(config.emptyText || crud.dict.label.empty);
                        },
                        append() {
                            return renderAppend();
                        }
                    }
                )
            );
        };
    }
});
Diff truncated after the above file
packages/crud/src/components/upsert/index.tsx packages/crud/src/emitter.ts packages/crud/src/entry.ts packages/crud/src/hooks/crud.ts packages/crud/src/hooks/index.ts packages/crud/src/index.ts packages/crud/src/locale/en.ts packages/crud/src/locale/index.ts packages/crud/src/locale/ja.ts packages/crud/src/locale/zh-cn.ts packages/crud/src/locale/zh-tw.ts packages/crud/src/main.ts packages/crud/src/provide.ts packages/crud/src/static/index.scss packages/crud/src/test/service.ts packages/crud/src/utils/form-hook.ts packages/crud/src/utils/global.ts packages/crud/src/utils/index.ts packages/crud/src/utils/mitt.ts packages/crud/src/utils/parse.tsx packages/crud/src/utils/vnode.tsx packages/crud/tsconfig.json packages/crud/types/components/add-btn/index.d.ts packages/crud/types/components/adv/btn.d.ts packages/crud/types/components/adv/search.d.ts packages/crud/types/components/context-menu/index.d.ts packages/crud/types/components/crud/helper.d.ts packages/crud/types/components/crud/index.d.ts packages/crud/types/components/dialog/index.d.ts packages/crud/types/components/error-message/index.d.ts packages/crud/types/components/filter/index.d.ts packages/crud/types/components/flex1/index.d.ts packages/crud/types/components/form-card/index.d.ts packages/crud/types/components/form-tabs/index.d.ts packages/crud/types/components/form/helper/action.d.ts packages/crud/types/components/form/helper/api.d.ts packages/crud/types/components/form/helper/index.d.ts packages/crud/types/components/form/helper/plugins.d.ts packages/crud/types/components/form/helper/tabs.d.ts packages/crud/types/components/form/index.d.ts packages/crud/types/components/index.d.ts packages/crud/types/components/multi-delete-btn/index.d.ts packages/crud/types/components/pagination/index.d.ts packages/crud/types/components/refresh-btn/index.d.ts packages/crud/types/components/row/index.d.ts packages/crud/types/components/search-key/index.d.ts packages/crud/types/components/search/helper/plugins.d.ts packages/crud/types/components/search/index.d.ts packages/crud/types/components/table/helper/data.d.ts packages/crud/types/components/table/helper/header.d.ts packages/crud/types/components/table/helper/height.d.ts packages/crud/types/components/table/helper/index.d.ts packages/crud/types/components/table/helper/op.d.ts packages/crud/types/components/table/helper/plugins.d.ts packages/crud/types/components/table/helper/render.d.ts packages/crud/types/components/table/helper/row.d.ts packages/crud/types/components/table/helper/selection.d.ts packages/crud/types/components/table/helper/sort.d.ts packages/crud/types/components/table/index.d.ts packages/crud/types/components/upsert/index.d.ts packages/crud/types/emitter.d.ts packages/crud/types/entry.d.ts packages/crud/types/hooks/crud.d.ts packages/crud/types/hooks/index.d.ts packages/crud/types/locale/en.d.ts packages/crud/types/locale/index.d.ts packages/crud/types/locale/ja.d.ts packages/crud/types/locale/zh-cn.d.ts packages/crud/types/locale/zh-tw.d.ts packages/crud/types/main.d.ts packages/crud/types/provide.d.ts packages/crud/types/test/service.d.ts packages/crud/types/utils/form-hook.d.ts packages/crud/types/utils/global.d.ts packages/crud/types/utils/index.d.ts packages/crud/types/utils/mitt.d.ts packages/crud/types/utils/parse.d.ts packages/crud/types/utils/vnode.d.ts packages/crud/vite.config.ts packages/crud/yarn.lock packages/vite-plugin/.eslintrc.js packages/vite-plugin/.prettierrc packages/vite-plugin/client.d.ts packages/vite-plugin/dist/base.d.ts packages/vite-plugin/dist/config.d.ts packages/vite-plugin/dist/ctx/index.d.ts packages/vite-plugin/dist/demo.d.ts packages/vite-plugin/dist/eps/flatten.d.ts packages/vite-plugin/dist/eps/index.d.ts packages/vite-plugin/dist/file/index.d.ts packages/vite-plugin/dist/index.d.ts packages/vite-plugin/dist/index.js packages/vite-plugin/dist/plugin/index.d.ts packages/vite-plugin/dist/proxy/index.d.ts packages/vite-plugin/dist/svg/index.d.ts packages/vite-plugin/dist/tag/index.d.ts packages/vite-plugin/dist/test.d.ts packages/vite-plugin/dist/uniapp-x.d.ts packages/vite-plugin/dist/uniapp-x/code.d.ts packages/vite-plugin/dist/uniapp-x/color.d.ts packages/vite-plugin/dist/uniapp-x/config.d.ts packages/vite-plugin/dist/uniapp-x/flatten.d.ts packages/vite-plugin/dist/uniapp-x/index.d.ts packages/vite-plugin/dist/uniapp-x/tailwind.d.ts packages/vite-plugin/dist/uniapp-x/utils.d.ts packages/vite-plugin/dist/utils/index.d.ts packages/vite-plugin/dist/virtual.d.ts packages/vite-plugin/package.json packages/vite-plugin/rollup.config.js packages/vite-plugin/src/base.ts packages/vite-plugin/src/config.ts packages/vite-plugin/src/ctx/index.ts packages/vite-plugin/src/demo.ts packages/vite-plugin/src/eps/index.ts packages/vite-plugin/src/file/index.ts packages/vite-plugin/src/index.ts packages/vite-plugin/src/plugin/index.ts packages/vite-plugin/src/proxy/index.ts packages/vite-plugin/src/svg/index.ts packages/vite-plugin/src/tag/index.ts packages/vite-plugin/src/test.ts packages/vite-plugin/src/uniapp-x/code.ts packages/vite-plugin/src/uniapp-x/config.ts packages/vite-plugin/src/uniapp-x/flatten.ts packages/vite-plugin/src/uniapp-x/index.ts packages/vite-plugin/src/uniapp-x/tailwind.ts packages/vite-plugin/src/uniapp-x/utils.ts packages/vite-plugin/src/utils/index.ts packages/vite-plugin/src/virtual.ts packages/vite-plugin/tsconfig.json packages/vite-plugin/types/index.d.ts packages/vite-plugin/yarn.lock pnpm-lock.yaml pnpm-workspace.yaml postcss.config.js public/favicon.ico public/logo.png public/用户导入模版.xlsx src/App.vue src/config/dev.ts src/config/index.ts src/config/prod.ts src/config/proxy.ts src/cool/bootstrap/eps.ts src/cool/bootstrap/index.ts src/cool/bootstrap/module.ts src/cool/hooks/browser.ts src/cool/hooks/hmr.ts src/cool/hooks/index.ts src/cool/hooks/mitt.ts src/cool/index.ts src/cool/index.vue src/cool/module/index.ts src/cool/router/index.ts src/cool/service/base.ts src/cool/service/index.ts src/cool/service/request.ts src/cool/service/stream.ts src/cool/types/index.ts src/cool/utils/index.ts src/cool/utils/loading.ts src/cool/utils/storage.ts src/main.ts src/modules/base/components/avatar/index.tsx src/modules/base/components/code/json.vue src/modules/base/components/dept/check.vue src/modules/base/components/dept/select.vue src/modules/base/components/editor/index.tsx src/modules/base/components/icon/svg.vue src/modules/base/components/image/index.vue src/modules/base/components/link/index.vue src/modules/base/components/menu/check.vue src/modules/base/components/menu/file.vue src/modules/base/components/menu/icon.vue src/modules/base/components/menu/perms.vue src/modules/base/components/menu/select.vue src/modules/base/components/num/index.vue src/modules/base/config.ts src/modules/base/directives/permission.ts src/modules/base/index.ts src/modules/base/locales/en.json src/modules/base/locales/zh-cn.json src/modules/base/locales/zh-tw.json src/modules/base/pages/error/401.vue src/modules/base/pages/error/403.vue src/modules/base/pages/error/404.vue src/modules/base/pages/error/500.vue src/modules/base/pages/error/502.vue src/modules/base/pages/error/components/error-page.vue src/modules/base/pages/login/components/pic-captcha.vue src/modules/base/pages/login/index.vue src/modules/base/pages/login/static/bg.svg src/modules/base/pages/main/components/amenu.vue src/modules/base/pages/main/components/bmenu.tsx src/modules/base/pages/main/components/global.vue src/modules/base/pages/main/components/process.vue src/modules/base/pages/main/components/route-nav.vue src/modules/base/pages/main/components/slider.vue src/modules/base/pages/main/components/topbar.vue src/modules/base/pages/main/components/views.vue src/modules/base/pages/main/index.vue src/modules/base/static/css/index.scss src/modules/base/static/svg/amount.svg src/modules/base/static/svg/back.svg src/modules/base/static/svg/close-border.svg src/modules/base/static/svg/close.svg src/modules/base/static/svg/delete.svg src/modules/base/static/svg/edit.svg src/modules/base/static/svg/exit.svg src/modules/base/static/svg/expand.svg src/modules/base/static/svg/export.svg src/modules/base/static/svg/fail.svg src/modules/base/static/svg/fold.svg src/modules/base/static/svg/github.svg src/modules/base/static/svg/home.svg src/modules/base/static/svg/icon-activity.svg src/modules/base/static/svg/icon-amount.svg src/modules/base/static/svg/icon-app.svg src/modules/base/static/svg/icon-approve.svg src/modules/base/static/svg/icon-auth.svg src/modules/base/static/svg/icon-ban.svg src/modules/base/static/svg/icon-call.svg src/modules/base/static/svg/icon-camera.svg src/modules/base/static/svg/icon-card.svg src/modules/base/static/svg/icon-cart.svg src/modules/base/static/svg/icon-common.svg src/modules/base/static/svg/icon-component.svg src/modules/base/static/svg/icon-count.svg src/modules/base/static/svg/icon-crown.svg src/modules/base/static/svg/icon-data.svg src/modules/base/static/svg/icon-db.svg src/modules/base/static/svg/icon-delete.svg src/modules/base/static/svg/icon-dept.svg src/modules/base/static/svg/icon-design.svg src/modules/base/static/svg/icon-device.svg src/modules/base/static/svg/icon-dict.svg src/modules/base/static/svg/icon-discover.svg src/modules/base/static/svg/icon-doc.svg src/modules/base/static/svg/icon-download.svg src/modules/base/static/svg/icon-emoji.svg src/modules/base/static/svg/icon-favor.svg src/modules/base/static/svg/icon-file.svg src/modules/base/static/svg/icon-folder.svg src/modules/base/static/svg/icon-goods.svg src/modules/base/static/svg/icon-home.svg src/modules/base/static/svg/icon-hot.svg src/modules/base/static/svg/icon-info.svg src/modules/base/static/svg/icon-iot.svg src/modules/base/static/svg/icon-light.svg src/modules/base/static/svg/icon-like.svg src/modules/base/static/svg/icon-list.svg src/modules/base/static/svg/icon-local.svg src/modules/base/static/svg/icon-log.svg src/modules/base/static/svg/icon-map.svg src/modules/base/static/svg/icon-match.svg src/modules/base/static/svg/icon-menu.svg src/modules/base/static/svg/icon-monitor.svg src/modules/base/static/svg/icon-msg.svg src/modules/base/static/svg/icon-news.svg src/modules/base/static/svg/icon-notice.svg src/modules/base/static/svg/icon-params.svg src/modules/base/static/svg/icon-phone.svg src/modules/base/static/svg/icon-pic.svg src/modules/base/static/svg/icon-question.svg src/modules/base/static/svg/icon-quick.svg src/modules/base/static/svg/icon-rank.svg src/modules/base/static/svg/icon-reward.svg src/modules/base/static/svg/icon-search.svg src/modules/base/static/svg/icon-set.svg src/modules/base/static/svg/icon-tag.svg src/modules/base/static/svg/icon-task.svg src/modules/base/static/svg/icon-time.svg src/modules/base/static/svg/icon-tutorial.svg src/modules/base/static/svg/icon-unlock.svg src/modules/base/static/svg/icon-user.svg src/modules/base/static/svg/icon-video.svg src/modules/base/static/svg/icon-vip.svg src/modules/base/static/svg/icon-warn.svg src/modules/base/static/svg/icon-work.svg src/modules/base/static/svg/icon-workbench.svg src/modules/base/static/svg/image.svg src/modules/base/static/svg/import.svg src/modules/base/static/svg/left.svg src/modules/base/static/svg/my.svg src/modules/base/static/svg/order.svg src/modules/base/static/svg/play.svg src/modules/base/static/svg/plus-border.svg src/modules/base/static/svg/plus.svg src/modules/base/static/svg/refresh.svg src/modules/base/static/svg/right.svg src/modules/base/static/svg/screen-full.svg src/modules/base/static/svg/screen-normal.svg src/modules/base/static/svg/search.svg src/modules/base/static/svg/set.svg src/modules/base/static/svg/sort.svg src/modules/base/static/svg/stats.svg src/modules/base/static/svg/success.svg src/modules/base/static/svg/team.svg src/modules/base/static/svg/trend.svg src/modules/base/store/app.ts src/modules/base/store/index.ts src/modules/base/store/menu.ts src/modules/base/store/process.ts src/modules/base/store/user.ts src/modules/base/types/index.d.ts src/modules/base/utils/index.ts src/modules/base/utils/permission.ts src/modules/base/views/frame.vue src/modules/base/views/info.vue src/modules/base/views/log.vue src/modules/base/views/menu/components/exp.vue src/modules/base/views/menu/components/imp.vue src/modules/base/views/menu/index.vue src/modules/base/views/param.vue src/modules/base/views/role.vue src/modules/base/views/user/components/dept-list.vue src/modules/base/views/user/components/user-move.vue src/modules/base/views/user/index.vue src/modules/basicdata/locales/en.json src/modules/basicdata/locales/zh-cn.json src/modules/basicdata/locales/zh-tw.json src/modules/basicdata/views/driver.vue src/modules/basicdata/views/iot.vue src/modules/basicdata/views/site.vue src/modules/basicdata/views/sorter.vue src/modules/basicdata/views/vehicle.vue src/modules/basicdata/views/wastetype.vue src/modules/demo/config.ts src/modules/demo/directives/color.ts src/modules/demo/locales/en.json src/modules/demo/locales/zh-cn.json src/modules/demo/locales/zh-tw.json src/modules/demo/views/crud/components/adv-search/base.vue src/modules/demo/views/crud/components/adv-search/custom.vue src/modules/demo/views/crud/components/code.vue src/modules/demo/views/crud/components/crud/all.vue src/modules/demo/views/crud/components/crud/base.vue src/modules/demo/views/crud/components/crud/dict.vue src/modules/demo/views/crud/components/crud/event.vue src/modules/demo/views/crud/components/crud/select-table.vue src/modules/demo/views/crud/components/crud/service.vue src/modules/demo/views/crud/components/crud/user-select.vue src/modules/demo/views/crud/components/form/children.vue src/modules/demo/views/crud/components/form/component/index.vue src/modules/demo/views/crud/components/form/component/select-labels.vue src/modules/demo/views/crud/components/form/component/select-status.vue src/modules/demo/views/crud/components/form/component/select-work.vue src/modules/demo/views/crud/components/form/component/select-work2.vue src/modules/demo/views/crud/components/form/config.vue src/modules/demo/views/crud/components/form/crud.vue src/modules/demo/views/crud/components/form/disabled.vue src/modules/demo/views/crud/components/form/event.vue src/modules/demo/views/crud/components/form/group.vue src/modules/demo/views/crud/components/form/hidden.vue src/modules/demo/views/crud/components/form/layout.vue src/modules/demo/views/crud/components/form/open.vue src/modules/demo/views/crud/components/form/options.vue src/modules/demo/views/crud/components/form/plugin/index.vue src/modules/demo/views/crud/components/form/plugin/role.ts src/modules/demo/views/crud/components/form/required.vue src/modules/demo/views/crud/components/form/rules.vue src/modules/demo/views/crud/components/form/setFocus.vue src/modules/demo/views/crud/components/other/context-menu.vue src/modules/demo/views/crud/components/other/tips.vue src/modules/demo/views/crud/components/other/tsx/index.scss src/modules/demo/views/crud/components/other/tsx/index.tsx src/modules/demo/views/crud/components/search/base.vue src/modules/demo/views/crud/components/search/collapse.vue src/modules/demo/views/crud/components/search/custom.vue src/modules/demo/views/crud/components/search/layout.vue src/modules/demo/views/crud/components/search/plugin.vue src/modules/demo/views/crud/components/table/base.vue src/modules/demo/views/crud/components/table/children.vue src/modules/demo/views/crud/components/table/column-custom.vue src/modules/demo/views/crud/components/table/component/index.vue src/modules/demo/views/crud/components/table/component/user-info.vue src/modules/demo/views/crud/components/table/context-menu.vue src/modules/demo/views/crud/components/table/dict.vue src/modules/demo/views/crud/components/table/formatter.vue src/modules/demo/views/crud/components/table/hidden.vue src/modules/demo/views/crud/components/table/op.vue src/modules/demo/views/crud/components/table/plugin/base.vue src/modules/demo/views/crud/components/table/plugin/row-edit.vue src/modules/demo/views/crud/components/table/plugin/to-tree.vue src/modules/demo/views/crud/components/table/search.vue src/modules/demo/views/crud/components/table/selection.vue src/modules/demo/views/crud/components/table/slot.vue src/modules/demo/views/crud/components/table/span-method.vue src/modules/demo/views/crud/components/table/summary.vue src/modules/demo/views/crud/components/upsert/base.vue src/modules/demo/views/crud/components/upsert/event.vue src/modules/demo/views/crud/components/upsert/hook/index.vue src/modules/demo/views/crud/components/upsert/hook/reg-pca2.ts src/modules/demo/views/crud/components/upsert/mode.vue src/modules/demo/views/crud/index.vue src/modules/demo/views/home/components/category-ratio.vue src/modules/demo/views/home/components/count-effect.vue src/modules/demo/views/home/components/count-paid.vue src/modules/demo/views/home/components/count-user.vue src/modules/demo/views/home/components/count-views.vue src/modules/demo/views/home/components/hot-goods.vue src/modules/demo/views/home/components/tab-chart.vue src/modules/demo/views/home/index.vue src/modules/demo/views/tenant.vue src/modules/demo/views/test/route.vue src/modules/dict/config.ts src/modules/dict/index.ts src/modules/dict/locales/en.json src/modules/dict/locales/zh-cn.json src/modules/dict/locales/zh-tw.json src/modules/dict/store/dict.ts src/modules/dict/store/index.ts src/modules/dict/types/index.d.ts src/modules/dict/utils/index.ts src/modules/dict/views/list.vue src/modules/helper/components/ai-code/btn.vue src/modules/helper/components/ai-code/dev.vue src/modules/helper/components/auto-menu.vue src/modules/helper/components/auto-perms.vue src/modules/helper/config.ts src/modules/helper/hooks/index.ts src/modules/helper/hooks/menu.ts src/modules/helper/hooks/plugin.ts src/modules/helper/locales/en.json src/modules/helper/locales/zh-cn.json src/modules/helper/locales/zh-tw.json src/modules/helper/static/index.scss src/modules/helper/static/svg/icon-vue.svg src/modules/helper/types/index.d.ts src/modules/helper/views/ai-code.vue src/modules/helper/views/plugins.vue src/modules/push/locales/en.json src/modules/push/locales/zh-cn.json src/modules/push/locales/zh-tw.json src/modules/push/views/auditlog.vue src/modules/push/views/cameragps.vue src/modules/push/views/siteweight.vue src/modules/push/views/vehiclegps.vue src/modules/push/views/weight.vue src/modules/recycle/locales/en.json src/modules/recycle/locales/zh-cn.json src/modules/recycle/locales/zh-tw.json src/modules/recycle/views/data.vue src/modules/space/components/space-inner.vue src/modules/space/components/space.vue src/modules/space/config.ts src/modules/space/hooks/index.ts src/modules/space/locales/en.json src/modules/space/locales/zh-cn.json src/modules/space/locales/zh-tw.json src/modules/space/views/list.vue src/modules/task/components/logs.vue src/modules/task/locales/en.json src/modules/task/locales/zh-cn.json src/modules/task/locales/zh-tw.json src/modules/task/views/list.vue src/modules/user/components/user-select.vue src/modules/user/config.ts src/modules/user/locales/en.json src/modules/user/locales/zh-cn.json src/modules/user/locales/zh-tw.json src/modules/user/views/list.vue src/plugins/crud/comm/index.ts src/plugins/crud/components/column-custom/index.vue src/plugins/crud/components/date/picker.vue src/plugins/crud/components/date/text.vue src/plugins/crud/components/dict/index.tsx src/plugins/crud/components/number/range.vue src/plugins/crud/components/render/index.tsx src/plugins/crud/components/select/button.tsx src/plugins/crud/components/select/index.scss src/plugins/crud/components/select/index.tsx src/plugins/crud/components/select/table.vue src/plugins/crud/components/switch/index.tsx src/plugins/crud/components/text/index.tsx src/plugins/crud/components/user/select.vue src/plugins/crud/config.ts src/plugins/crud/index.ts src/plugins/crud/locales/en.json src/plugins/crud/locales/zh-cn.json src/plugins/crud/locales/zh-tw.json src/plugins/crud/plugins/form/index.ts src/plugins/crud/plugins/form/setFocus.ts src/plugins/crud/plugins/index.ts src/plugins/crud/plugins/search/index.ts src/plugins/crud/plugins/search/setAuto.ts src/plugins/crud/plugins/table/index.ts src/plugins/crud/plugins/table/rowEdit.tsx src/plugins/crud/plugins/table/toTree.ts src/plugins/dev-tools/components/account.vue src/plugins/dev-tools/components/dict.vue src/plugins/dev-tools/components/doc.vue src/plugins/dev-tools/components/eps.vue src/plugins/dev-tools/components/index.vue src/plugins/dev-tools/components/proxy.vue src/plugins/dev-tools/config.ts src/plugins/dev-tools/locales/en.json src/plugins/dev-tools/locales/zh-cn.json src/plugins/dev-tools/locales/zh-tw.json src/plugins/dev-tools/static/cool.png src/plugins/dev-tools/static/echarts.png src/plugins/dev-tools/static/element-plus.png src/plugins/dev-tools/static/lodash.png src/plugins/dev-tools/static/logo.png src/plugins/dev-tools/static/pinia.png src/plugins/dev-tools/static/tailwindcss.png src/plugins/dev-tools/static/vite.png src/plugins/dev-tools/static/vue.png src/plugins/dev-tools/utils/index.ts src/plugins/distpicker/components/index.tsx src/plugins/distpicker/config.ts src/plugins/distpicker/data/pca.json src/plugins/distpicker/demo/base.vue src/plugins/echarts/config.ts src/plugins/editor-preview/components/preview.vue src/plugins/editor-preview/config.ts src/plugins/editor-preview/demo/base.vue src/plugins/editor-preview/locales/en.json src/plugins/editor-preview/locales/zh-cn.json src/plugins/editor-preview/locales/zh-tw.json src/plugins/editor-wang/components/wang.vue src/plugins/editor-wang/config.ts src/plugins/editor-wang/demo/base.vue src/plugins/editor-wang/locales/en.json src/plugins/editor-wang/locales/zh-cn.json src/plugins/editor-wang/locales/zh-tw.json src/plugins/element-ui/config.ts src/plugins/element-ui/css/index.scss src/plugins/excel/components/export-btn.tsx src/plugins/excel/components/import-btn.vue src/plugins/excel/config.ts src/plugins/excel/demo/base.vue src/plugins/excel/locales/en.json src/plugins/excel/locales/zh-cn.json src/plugins/excel/locales/zh-tw.json src/plugins/excel/utils/index.ts src/plugins/github/components/code.vue src/plugins/github/config.ts src/plugins/i18n/components/switch.vue src/plugins/i18n/config.ts src/plugins/i18n/index.ts src/plugins/i18n/static/svg/lang.svg src/plugins/iconfont/config.ts src/plugins/iconfont/index.ts src/plugins/iconfont/utils/index.ts src/plugins/tailwind/config.ts src/plugins/tailwind/static/index.css src/plugins/theme/components/theme.vue src/plugins/theme/config.ts src/plugins/theme/hooks/index.ts src/plugins/theme/index.ts src/plugins/theme/locales/en.json src/plugins/theme/locales/zh-cn.json src/plugins/theme/locales/zh-tw.json src/plugins/theme/static/css/index.scss src/plugins/theme/static/svg/dark.svg src/plugins/theme/static/svg/light.svg src/plugins/theme/static/svg/theme.svg src/plugins/theme/types/index.d.ts src/plugins/theme/utils/index.ts src/plugins/upload/components/upload-item/index.vue src/plugins/upload/components/upload-item/viewer.vue src/plugins/upload/components/upload.vue src/plugins/upload/config.ts src/plugins/upload/demo/base.vue src/plugins/upload/demo/check.vue src/plugins/upload/demo/custom.vue src/plugins/upload/demo/drag.vue src/plugins/upload/demo/file.vue src/plugins/upload/demo/multiple.vue src/plugins/upload/demo/small.vue src/plugins/upload/demo/space.vue src/plugins/upload/hooks/index.ts src/plugins/upload/index.ts src/plugins/upload/locales/en.json src/plugins/upload/locales/zh-cn.json src/plugins/upload/locales/zh-tw.json src/plugins/upload/static/svg/audio.svg src/plugins/upload/static/svg/excel.svg src/plugins/upload/static/svg/file.svg src/plugins/upload/static/svg/image.svg src/plugins/upload/static/svg/pdf.svg src/plugins/upload/static/svg/ppt.svg src/plugins/upload/static/svg/rar.svg src/plugins/upload/static/svg/video.svg src/plugins/upload/static/svg/word.svg src/plugins/upload/types/index.d.ts src/plugins/upload/utils/index.ts src/plugins/view/components/group.vue src/plugins/view/components/head.vue src/plugins/view/config.ts src/plugins/view/demo/group.vue src/plugins/view/demo/head.vue src/plugins/view/hooks/group.ts src/plugins/view/hooks/index.ts src/plugins/view/index.ts src/plugins/view/locales/en.json src/plugins/view/locales/zh-cn.json src/plugins/view/locales/zh-tw.json src/plugins/view/types/index.d.ts src/shims-vue.d.ts tailwind.config.js tsconfig.json vite.config.ts