wangzhiqiang 5 bulan lalu
melakukan
6946bdee3b
100 mengubah file dengan 10220 tambahan dan 0 penghapusan
  1. 9 0
      .env.development
  2. 4 0
      .env.production
  3. 4 0
      .env.staging
  4. 25 0
      .gitignore
  5. 38 0
      README.md
  6. 16 0
      index.html
  7. 503 0
      mock/backmenu.js
  8. 26 0
      mock/card.js
  9. 27 0
      mock/menu.js
  10. 73 0
      mock/systemUser.js
  11. 136 0
      mock/table.js
  12. 84 0
      mock/user.js
  13. 336 0
      mock/work.js
  14. 16 0
      mockProdServer.js
  15. 3980 0
      package-lock.json
  16. 37 0
      package.json
  17. TEMPAT SAMPAH
      public/favicon.ico
  18. 42 0
      src/App.vue
  19. 10 0
      src/api/common.js
  20. 51 0
      src/api/login.js
  21. 91 0
      src/api/riskModule.js
  22. 49 0
      src/api/user.js
  23. 46 0
      src/api/userModule.js
  24. TEMPAT SAMPAH
      src/assets/images/401.gif
  25. TEMPAT SAMPAH
      src/assets/images/404.png
  26. TEMPAT SAMPAH
      src/assets/images/404_cloud.png
  27. TEMPAT SAMPAH
      src/assets/login/bg.png
  28. TEMPAT SAMPAH
      src/assets/login/left.jpg
  29. TEMPAT SAMPAH
      src/assets/logo.png
  30. 284 0
      src/assets/style/common.scss
  31. 37 0
      src/assets/style/transition.scss
  32. 6 0
      src/assets/svg/read.md
  33. 31 0
      src/components/SvgIcon/index.vue
  34. 42 0
      src/components/charts/index.vue
  35. 182 0
      src/components/from/index.vue
  36. 66 0
      src/components/layer/index.vue
  37. 12 0
      src/components/menu/index.vue
  38. 98 0
      src/components/table/index.vue
  39. 18 0
      src/config/index.js
  40. 26 0
      src/directive/clickOutside/index.js
  41. 33 0
      src/directive/copy/index.js
  42. 28 0
      src/directive/debounce/index.js
  43. 141 0
      src/directive/drag/index.js
  44. 151 0
      src/directive/dragable/index.js
  45. 48 0
      src/directive/longpress/index.js
  46. 30 0
      src/directive/waterMarker/index.js
  47. 219 0
      src/hooks/useGetDictList.js
  48. 65 0
      src/layout/Header/Breadcrumb.vue
  49. 31 0
      src/layout/Header/functionList/fullscreen.vue
  50. 25 0
      src/layout/Header/functionList/github.vue
  51. 55 0
      src/layout/Header/functionList/sizeChange.vue
  52. 177 0
      src/layout/Header/functionList/theme.vue
  53. 77 0
      src/layout/Header/functionList/theme/theme-color.vue
  54. 130 0
      src/layout/Header/functionList/theme/theme-icon.vue
  55. 168 0
      src/layout/Header/index.vue
  56. 92 0
      src/layout/Header/passwordLayer.vue
  57. 47 0
      src/layout/Logo/index.vue
  58. 41 0
      src/layout/Menu/Link.vue
  59. 102 0
      src/layout/Menu/MenuItem.vue
  60. 118 0
      src/layout/Menu/index.vue
  61. 308 0
      src/layout/Tabs/index.vue
  62. 108 0
      src/layout/Tabs/item.vue
  63. 11 0
      src/layout/Tabs/tabsHook.js
  64. 145 0
      src/layout/index.vue
  65. 31 0
      src/main.js
  66. 46 0
      src/router/createNode.js
  67. 75 0
      src/router/index.js
  68. 21 0
      src/router/modules/agentModule.js
  69. 19 0
      src/router/modules/dashboard.js
  70. 20 0
      src/router/modules/formworkErection.js
  71. 21 0
      src/router/modules/operatorModule.js
  72. 21 0
      src/router/modules/riskModule.js
  73. 44 0
      src/router/modules/system.js
  74. 39 0
      src/router/modules/userModule.js
  75. 21 0
      src/router/modules/withdrawModule.js
  76. 55 0
      src/router/permission.js
  77. 48 0
      src/router/permission/back.js
  78. 51 0
      src/router/permission/backConfig.js
  79. 27 0
      src/router/permission/front.js
  80. 116 0
      src/router/reload.vue
  81. 29 0
      src/store/index.js
  82. 44 0
      src/store/modules/app.js
  83. 40 0
      src/store/modules/keepAlive.js
  84. 74 0
      src/store/modules/user.js
  85. 39 0
      src/store/plugins/persistent.js
  86. 130 0
      src/theme/index.js
  87. 52 0
      src/theme/index.scss
  88. 7 0
      src/theme/modules/chinese/index.scss
  89. TEMPAT SAMPAH
      src/theme/modules/chinese/screen.png
  90. 23 0
      src/theme/modules/dark.scss
  91. 37 0
      src/utils/index.js
  92. 12 0
      src/utils/system/nprogress.js
  93. 87 0
      src/utils/system/request.js
  94. 15 0
      src/utils/system/statistics.js
  95. 5 0
      src/utils/system/title.js
  96. 46 0
      src/utils/tab/index.js
  97. 9 0
      src/views/main/agentModule/index.vue
  98. 35 0
      src/views/main/dashboard/components/card/index.vue
  99. 82 0
      src/views/main/dashboard/components/card/row.vue
  100. 44 0
      src/views/main/dashboard/components/charts/barChart.vue

+ 9 - 0
.env.development

@@ -0,0 +1,9 @@
+ENV = 'development'
+
+# 线上测试
+# VITE_BASE_URL = 'http://advise.ytmdm.com/yt-gateway'
+VITE_BASE_URL = 'http://119.45.71.139:25001'
+
+# 本地 
+# VITE_BASE_URL = 'http://192.168.1.9:25001'
+

+ 4 - 0
.env.production

@@ -0,0 +1,4 @@
+ENV = 'production'
+
+# VITE_BASE_URL = 'http://advise.ytmdm.com/yt-gateway'
+VITE_BASE_URL = 'http://119.45.71.139:25001'

+ 4 - 0
.env.staging

@@ -0,0 +1,4 @@
+ENV = 'staging'
+
+# VITE_BASE_URL = 'http://advise.ytmdm.com/yt-gateway'
+VITE_BASE_URL = 'http://119.45.71.139:25001'

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+

+ 38 - 0
README.md

@@ -0,0 +1,38 @@
+<h1 align="center">易推网络渠道商营销管理系统</h1>
+
+
+#### 主要技术栈
+
+- MVVM框架:vue v3
+- 工程化管理:vite v2
+- UI框架:element-plus
+- 路由管理:vue-router v4
+- 状态管理:vuex v4
+- 数据请求:axios
+- 实用工具库:@vueuse/core
+
+## 使用
+1. 安装依赖,国内推荐使用cnpm或tyarn,国外推荐使用npm或yarn
+
+   ```
+   npm install
+   ``` 
+
+2. 运行
+
+   ```
+   npm run dev 或 npm run start
+   ```
+  
+
+3. 打包
+
+   ```
+   npm run build
+   ```
+
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 16 - 0
index.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
+    <link rel="stylesheet" href="//at.alicdn.com/t/font_2570680_gkyjimtz1d.css">
+    <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4934570_j516p363jf.css">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title></title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 503 - 0
mock/backmenu.js

@@ -0,0 +1,503 @@
+/*
+ * @Date: 2023-03-10 19:57:44
+ * @Description: 
+ */
+export default [
+  {
+    url: `/mock/menu/list`,
+    method: 'post',
+    response: () => {
+      return {
+        code: 200,
+        data: {
+          list: [
+            {
+              "path": "/",
+              "redirect": "/dashboard",
+              "meta": {
+                "title": "dashboard",
+                "icon": "sfont system-home"
+              },
+              "children": [
+                {
+                  "path": "dashboard",
+                  "meta": {
+                    "title": "首页",
+                    "icon": "sfont system-home",
+                    "hideClose": true
+                  },
+                  "component": "dashboard_dashboard"
+                }
+              ]
+            },
+            {
+              "path": "/document",
+              "redirect": "/document/intro",
+              "meta": {
+                "title": "文档",
+                "icon": "sfont system-document"
+              },
+              "children": [
+                {
+                  "path": "intro",
+                  "meta": {
+                    "title": "使用说明"
+                  },
+                  "component": "document_intro"
+                },
+                {
+                  "path": "function",
+                  "meta": {
+                    "title": "功能说明"
+                  },
+                  "component": "document_function"
+                },
+                {
+                  "path": "menu",
+                  "meta": {
+                    "title": "路由菜单配置"
+                  },
+                  "component": "document_menu"
+                },
+                {
+                  "path": "keep-alive",
+                  "meta": {
+                    "title": "keepAlive使用说明"
+                  },
+                  "component": "document_keep-alive"
+                },
+                {
+                  "path": "crud",
+                  "meta": {
+                    "title": "数据表格的增删改查"
+                  },
+                  "component": "document_crud"
+                },
+                {
+                  "path": "theme",
+                  "meta": {
+                    "title": "自定义主题"
+                  },
+                  "component": "document_theme"
+                },
+                {
+                  "path": "systemfont",
+                  "meta": {
+                    "title": "项目图标的说明"
+                  },
+                  "component": "document_systemfont"
+                },
+                {
+                  "path": "api",
+                  "meta": {
+                    "title": "接口说明文档"
+                  },
+                  "component": "document_api"
+                }
+              ]
+            },
+            {
+              "path": "/component",
+              "redirect": "/component/table",
+              "meta": {
+                "title": "组件",
+                "icon": "sfont system-component"
+              },
+              "alwayShow": true,
+              "children": [
+                {
+                  "path": "button",
+                  "meta": {
+                    "title": "按钮"
+                  },
+                  "component": "component_button"
+                },
+                {
+                  "path": "wordEditor",
+                  "meta": {
+                    "title": "文本编辑器"
+                  },
+                  "component": "component_wordEditor"
+                },
+                {
+                  "path": "mdEditor",
+                  "meta": {
+                    "title": "md编辑器"
+                  },
+                  "component": "component_mdEditor"
+                },
+                {
+                  "path": "codeEditor",
+                  "meta": {
+                    "title": "代码编辑器"
+                  },
+                  "component": "component_codeEditor"
+                },
+                {
+                  "path": "jsonEditor",
+                  "meta": {
+                    "title": "JSON编辑器"
+                  },
+                  "component": "component_jsonEditor"
+                },
+                {
+                  "path": "dragPane",
+                  "meta": {
+                    "title": "可拖拽面板"
+                  },
+                  "component": "component_dragPane"
+                },
+                {
+                  "path": "map",
+                  "meta": {
+                    "title": "地图组件"
+                  },
+                  "component": "component_map"
+                },
+                {
+                  "path": "cutPhoto",
+                  "meta": {
+                    "title": "弹窗图片裁剪"
+                  },
+                  "component": "component_cutPhoto"
+                },
+                {
+                  "path": "rightMenu",
+                  "meta": {
+                    "title": "右键菜单"
+                  },
+                  "component": "component_rightMenu"
+                },
+                {
+                  "path": "exportExcel",
+                  "meta": {
+                    "title": "导出文件"
+                  },
+                  "component": "component_exportExcel"
+                }
+              ]
+            },
+            {
+              "path": "/pages",
+              "redirect": "/pages/crudTable",
+              "meta": {
+                "title": "页面",
+                "icon": "sfont system-page"
+              },
+              "alwayShow": true,
+              "children": [
+                {
+                  "path": "crudTable",
+                  "meta": {
+                    "title": "业务表格",
+                    "cache": false
+                  },
+                  "component": "pages_crudTable"
+                },
+                {
+                  "path": "categoryTable",
+                  "meta": {
+                    "title": "分类联动表格",
+                    "cache": true
+                  },
+                  "component": "pages_categoryTable"
+                },
+                {
+                  "path": "treeTable",
+                  "meta": {
+                    "title": "树联动表格",
+                    "cache": true
+                  },
+                  "component": "pages_treeTable"
+                },
+                {
+                  "path": "card",
+                  "meta": {
+                    "title": "卡片列表",
+                    "cache": true
+                  },
+                  "component": "pages_card"
+                },
+                {
+                  "path": "work",
+                  "meta": {
+                    "title": "工作进度"
+                  },
+                  "component": "pages_work"
+                },
+                {
+                  "path": "blog",
+                  "meta": {
+                    "title": "外部链接"
+                  },
+                  "component": "pages_blog"
+                }
+              ]
+            },
+            {
+              "path": "/menu",
+              "redirect": "/menu/menu-1",
+              "alwayShow": true,
+              "meta": {
+                "title": "多级嵌套菜单",
+                "icon": "sfont system-menu"
+              },
+              "children": [
+                {
+                  "path": "menu-1",
+                  "redirect": "/menu/menu-1/menu-1-1",
+                  "meta": {
+                    "title": "二级菜单1"
+                  },
+                  "children": [
+                    {
+                      "path": "menu-1-1",
+                      "redirect": "/menu/menu-1/menu-1-1/menu-1-1-1",
+                      "meta": {
+                        "title": "三级菜单1-1"
+                      },
+                      "children": [
+                        {
+                          "path": "menu-1-1-1",
+                          "meta": {
+                            "title": "四级菜单1-1-1"
+                          },
+                          "component": "menu_menu-1-1-1"
+                        },
+                        {
+                          "path": "menu-1-1-2",
+                          "meta": {
+                            "title": "四级菜单1-1-2"
+                          },
+                          "component": "menu_menu-1-1-2"
+                        }
+                      ]
+                    },
+                    {
+                      "path": "menu-1-2",
+                      "meta": {
+                        "title": "三级菜单1-2"
+                      },
+                      "component": "menu_menu-1-2"
+                    }
+                  ]
+                },
+                {
+                  "path": "menu-2",
+                  "meta": {
+                    "title": "二级菜单2"
+                  },
+                  "component": "menu_menu-2"
+                },
+                {
+                  "path": "menu-3",
+                  "meta": {
+                    "title": "二级菜单3"
+                  },
+                  "component": "menu_menu-3"
+                }
+              ]
+            },
+            {
+              "path": "/directive",
+              "redirect": "/directive/copy",
+              "meta": {
+                "title": "自定义指令",
+                "icon": "sfont system-zidingyi"
+              },
+              "children": [
+                {
+                  "path": "dragable",
+                  "meta": {
+                    "title": "拖拽指令:v-dragable"
+                  },
+                  "component": "directive_dragable"
+                },
+                {
+                  "path": "copy",
+                  "meta": {
+                    "title": "复制指令:v-copy"
+                  },
+                  "component": "directive_copy"
+                },
+                {
+                  "path": "waterMarker",
+                  "meta": {
+                    "title": "水印指令:v-waterMarker"
+                  },
+                  "component": "directive_waterMarker"
+                },
+                {
+                  "path": "longpress",
+                  "meta": {
+                    "title": "长按指令:v-longpress"
+                  },
+                  "component": "directive_longpress"
+                },
+                {
+                  "path": "debounce",
+                  "meta": {
+                    "title": "按钮防抖指令:v-debounce"
+                  },
+                  "component": "directive_debounce"
+                },
+                {
+                  "path": "scroll",
+                  "meta": {
+                    "title": "下拉加载指令:v-infinite-scroll"
+                  },
+                  "component": "directive_scroll"
+                },
+                {
+                  "path": "clickOutside",
+                  "meta": {
+                    "title": "点击外部区域:v-click-outside"
+                  },
+                  "component": "directive_clickOutside"
+                }
+              ]
+            },
+            {
+              "path": "/echarts",
+              "redirect": "/echarts/dragable",
+              "meta": {
+                "title": "echarts图表",
+                "icon": "sfont system-chart"
+              },
+              "children": [
+                {
+                  "path": "bar",
+                  "meta": {
+                    "title": "柱状图"
+                  },
+                  "component": "echarts_bar"
+                },
+                {
+                  "path": "line",
+                  "meta": {
+                    "title": "折线图"
+                  },
+                  "component": "echarts_line"
+                },
+                {
+                  "path": "pie",
+                  "meta": {
+                    "title": "饼图"
+                  },
+                  "component": "echarts_pie"
+                },
+                {
+                  "path": "radar",
+                  "meta": {
+                    "title": "雷达图"
+                  },
+                  "component": "echarts_radar"
+                },
+                {
+                  "path": "map",
+                  "meta": {
+                    "title": "地图"
+                  },
+                  "component": "echarts_map"
+                }
+              ]
+            },
+            {
+              "path": "/systemManage",
+              "redirect": "/systemManage/menu",
+              "meta": {
+                "title": "系统管理",
+                "icon": "sfont system-xitongzhuangtai"
+              },
+              "alwayShow": true,
+              "children": [
+                {
+                  "path": "menu",
+                  "meta": {
+                    "title": "菜单"
+                  },
+                  "component": "systemManage_menu"
+                },
+                {
+                  "path": "role",
+                  "meta": {
+                    "title": "角色"
+                  },
+                  "component": "systemManage_role"
+                },
+                {
+                  "path": "user",
+                  "meta": {
+                    "title": "用户"
+                  },
+                  "component": "systemManage_user"
+                }
+              ]
+            },
+            {
+              "path": "/print",
+              "redirect": "/print/js",
+              "meta": {
+                "title": "打印功能",
+                "icon": "sfont system-24gl-printer"
+              },
+              "children": [
+                {
+                  "path": "js",
+                  "meta": {
+                    "title": "JS打印事件",
+                    "icon": "sfont system-24gl-printer"
+                  },
+                  "component": "print_js"
+                }
+              ]
+            },
+            {
+              "path": "/community",
+              "redirect": "/community/qq",
+              "meta": {
+                "title": "社区的力量",
+                "icon": "sfont system-shequ"
+              },
+              "children": [
+                {
+                  "path": "qq",
+                  "meta": {
+                    "title": "QQ交流群"
+                  },
+                  "component": "community_qq"
+                },
+                {
+                  "path": "site",
+                  "meta": {
+                    "title": "vue3最新资源库"
+                  },
+                  "component": "community_site"
+                }
+              ]
+            },
+            {
+              "path": "/tab",
+              "redirect": "/tab/index",
+              "meta": {
+                "title": "打印功能",
+                "icon": "sfont system-24gl-printer"
+              },
+              "children": [
+                {
+                  "path": "index",
+                  "meta": {
+                    "title": "tab事件公用",
+                    "icon": "sfont system-24gl-printer"
+                  },
+                  "component": "tab_index"
+                }
+              ]
+            }
+          ]
+        },
+        msg: ''
+      };
+    }
+  },
+]

+ 26 - 0
mock/card.js

@@ -0,0 +1,26 @@
+export default [
+  {
+    url: `/mock/card/list`,
+    method: 'post',
+    response: ({ body }) => {
+      const { page, pageSize } = body
+      return {
+        code: 200,
+        data: {
+          [`list|${pageSize}`]: [{
+            'id|+1': 0,
+            title: '@ctitle',
+            image: 'http://blog.51weblove.com/wp-content/uploads/2019/03/2019032323331541.jpg',
+            time: '@date(yyyy-MM-dd hh:mm:ss)'
+          }],
+          pager: {
+            page: page,
+            pageSize: pageSize,
+            total: 198
+          }
+        },
+        msg: ''
+      };
+    }
+  },
+]

+ 27 - 0
mock/menu.js

@@ -0,0 +1,27 @@
+/** 给接口使用 */
+const menu = [
+  {
+    path: '/',
+    meta: { title: 'dashboard', icon: 'sfont icon-home' },
+    children: [
+      {
+        path: 'dashboard',
+        meta: { title: '首页', icon: 'sfont icon-home', hideClose: true }
+      }
+    ]
+  }
+]
+
+export default [
+  /** 需要展示的菜单模拟接口 */
+  {
+    url: `/mock/menu/list`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: menu
+      }
+    }
+  }
+]

+ 73 - 0
mock/systemUser.js

@@ -0,0 +1,73 @@
+const roles = ['系统管理员', '平台管理员', '数据统计人员', '信息录入人员', '普通人员']
+export default [
+  {
+    url: `/mock/system/user/list`,
+    method: 'post',
+    response: ({ body }) => {
+      const { page, pageSize } = body
+      return {
+        code: 200,
+        data: {
+          [`list|${pageSize}`]: [{
+            'id|+1': 0,
+            name: '@name',
+            nickName: '@cname',
+            'status|1': [0,1],
+            'role|1': roles,
+            'isAdmin|1': [0,1]
+          }],
+          pager: {
+            page: page,
+            pageSize: pageSize,
+            total: 198
+          }
+        },
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/system/user/add`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/system/user/update`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/system/user/updateStatus`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/system/user/del`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+]

+ 136 - 0
mock/table.js

@@ -0,0 +1,136 @@
+export default [
+  {
+    url: `/mock/table/list`,
+    method: 'post',
+    response: ({ body }) => {
+      const { page, pageSize } = body
+      return {
+        code: 200,
+        data: {
+          [`list|${pageSize}`]: [{
+            'id|+1': 0,
+            'name': '@cname',
+            "number|+1": 500,
+            "choose|1": [1, 2, 3, 4],
+            "radio|1": [1, 2, 3]
+          }],
+          pager: {
+            page: page,
+            pageSize: pageSize,
+            total: 198
+          }
+        },
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/table/category`,
+    method: 'post',
+    response: ({ body }) => {
+      const { page, pageSize } = body
+      return {
+        code: 200,
+        data: {
+          [`list|${pageSize}`]: [{
+            'id|+1': 100 * page,
+            'name': '@ctitle'
+          }],
+          pager: {
+            page: page,
+            pageSize: pageSize,
+            total: 100
+          }
+        },
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/table/tree`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: [{
+          label: '人事部',
+          id: 1,
+          'children|5': [{
+            label: '@cname',
+            'id|+1': 10
+          }]
+        }, {
+          label: '研发部',
+          id: 2,
+          children: [{
+            label: '前端',
+            id: 3,
+            'children|5': [{
+              label: '@cname',
+              'id|+1': 20
+            }]
+          }, {
+            label: '后端',
+            id: 4,
+            'children|5': [{
+              label: '@cname',
+              'id|+1': 30
+            }]
+          }]
+        }, {
+          label: '运营部',
+          id: 5,
+          children: [{
+            label: '市场运营',
+            id: 6,
+            'children|5': [{
+              label: '@cname',
+              'id|+1': 40
+            }]
+          }, {
+            label: '互联网营销',
+            id: 7,
+            'children|5': [{
+              label: '@cname',
+              'id|+1': 50
+            }]
+          }]
+        }],
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/table/add`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/table/update`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+  {
+    url: `/mock/table/del`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {},
+        msg: ''
+      };
+    }
+  },
+]

+ 84 - 0
mock/user.js

@@ -0,0 +1,84 @@
+const users = [
+  { name: 'admin', password: '123456', token: 'admin', info: {
+    name: '系统管理员'
+  }},
+  { name: 'editor', password: '123456', token: 'editor', info: {
+    name: '编辑人员'
+  }},
+  { name: 'test', password: '123456', token: 'test', info: {
+    name: '测试人员'
+  }},
+]
+export default [
+  {
+    url: `/mock/user/login`,
+    method: 'post',
+    response: ({ body }) => {
+      const user = users.find(user => {
+        return body.name === user.name && body.password === user.password
+      })
+      if (user) {
+        return {
+          code: 200,
+          data: {
+            token: user.token,
+          },
+        };
+      } else {
+        return {
+          code: 401,
+          data: {},
+          msg: '用户名或密码错误'
+        };
+      }
+      
+    }
+  },
+  {
+    url: `/mock/user/info`,
+    method: 'post',
+    response: ({ body }) => {
+      const { token } = body
+      const info = users.find(user => {
+        return user.token === token
+      }).info
+      if (info) {
+        return {
+          code: 200,
+          data: {
+            info: info
+          },
+        };
+      } else {
+        return {
+          code: 403,
+          data: {},
+          msg: '无访问权限'
+        };
+      }
+      
+    }
+  },
+  {
+    url: `/mock/user/out`,
+    method: 'post',
+    response: () => {
+      return {
+        code: 200,
+        data: {},
+        msg: 'success'
+      };
+    }
+  },
+  {
+    url: `/mock/user/passwordChange`,
+    method: 'post',
+    response: () => {
+      return {
+        code: 200,
+        data: {},
+        msg: 'success'
+      };
+    }
+  },
+]

+ 336 - 0
mock/work.js

@@ -0,0 +1,336 @@
+export default [
+  {
+    url: `/mock/work/list`,
+    method: 'post',
+    response: ({ body }) => {
+      return {
+        code: 200,
+        data: {
+          list: [
+            {
+              name: '待处理',
+              children: [
+                {
+                  id: 101,
+                  tags: ['优化'],
+                  name: '父级菜单显示为白色',
+                  options: [
+                    '类型:系统优化'
+                  ]
+                }, {
+                  id: 102,
+                  tags: ['优化'],
+                  name: '菜单国际化处理',
+                  options: [
+                    '类型:系统优化'
+                  ]
+                }, {
+                  id: 103,
+                  tags: ['优化'],
+                  name: '系统全局国际化',
+                  options: [
+                    '类型:系统优化'
+                  ]
+                }]
+            },
+            {
+              name: '处理中',
+              children: [{
+                id: 12,
+                tags: ['新增'],
+                name: '系统管理-菜单管理',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 104,
+                tags: ['新增'],
+                name: '系统管理-字典管理',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 105,
+                tags: ['新增'],
+                name: '系统管理-用户管理',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 13,
+                tags: ['新增'],
+                name: '系统管理-角色管理',
+                options: [
+                  '类型:页面'
+                ]
+              },
+                
+              ]
+            },
+            {
+              name: '测试中',
+              children: [{
+                id: 11,
+                tags: ['新增'],
+                name: '右键菜单',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 14,
+                tags: ['优化'],
+                name: 'tab标签栏Keep-alive',
+                options: [
+                  '类型:系统优化'
+                ]
+              },{
+                id: 15,
+                tags: ['优化'],
+                name: 'tab标签栏切换时自动居中处理tab标签栏切换时自动居中处理',
+                options: [
+                  '类型:系统优化'
+                ]
+              },{
+                id: 6,
+                tags: ['新增'],
+                name: '柱状图',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 7,
+                tags: ['新增'],
+                name: '折线图',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 8,
+                tags: ['新增'],
+                name: '饼图',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 9,
+                tags: ['新增'],
+                name: '雷达图',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 10,
+                tags: ['新增'],
+                name: '地图',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 1,
+                tags: ['新增'],
+                name: '拖拽指令',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 2,
+                tags: ['新增'],
+                name: '复制指令',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 3,
+                tags: ['新增'],
+                name: '防抖指令',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 4,
+                tags: ['新增'],
+                name: '长按指令',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 5,
+                tags: ['新增'],
+                name: '下拉加载',
+                options: [
+                  '类型:页面'
+                ]
+              },]
+            },
+            {
+              name: '已完成',
+              children: [{
+                id: 16,
+                tags: ['新增'],
+                name: '页面-工作进度',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 18,
+                tags: ['系统'],
+                name: '路由管理router',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 19,
+                tags: ['系统'],
+                name: '存储方案store',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 20,
+                tags: ['系统'],
+                name: '登录管理',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 21,
+                tags: ['系统'],
+                name: '菜单配置',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 22,
+                tags: ['系统'],
+                name: '国际化解决方案',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 23,
+                tags: ['系统'],
+                name: '尺寸切换',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 24,
+                tags: ['系统'],
+                name: '全屏功能',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 25,
+                tags: ['系统'],
+                name: '导航栏功能',
+                options: [
+                  '类型:系统'
+                ]
+              },{
+                id: 26,
+                tags: ['新增'],
+                name: '首页',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 27,
+                tags: ['新增'],
+                name: '首页',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 28,
+                tags: ['新增'],
+                name: '组件-按钮',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 29,
+                tags: ['新增'],
+                name: '组件-文本编辑器',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 30,
+                tags: ['新增'],
+                name: '组件-代码编辑器',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 31,
+                tags: ['新增'],
+                name: '组件-md编辑器',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 32,
+                tags: ['新增'],
+                name: '组件-JSON编辑器',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 33,
+                tags: ['新增'],
+                name: '组件-可拖拽面板',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 34,
+                tags: ['新增'],
+                name: '组件-地图组件',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 35,
+                tags: ['新增'],
+                name: '组件-弹窗图片裁剪',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 36,
+                tags: ['新增'],
+                name: '多级嵌套表单',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 37,
+                tags: ['新增'],
+                name: '页面-业务表格',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 38,
+                tags: ['新增'],
+                name: '页面-卡片列表',
+                options: [
+                  '类型:页面'
+                ]
+              },{
+                id: 39,
+                tags: ['新增'],
+                name: '页面-百度一下',
+                options: [
+                  '类型:页面'
+                ]
+              }]
+            }
+          ]
+        },
+        msg: ''
+      };
+    }
+  },
+]

+ 16 - 0
mockProdServer.js

@@ -0,0 +1,16 @@
+import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
+import userModule from './mock/user'
+import tableModule from './mock/table'
+import cardModule from './mock/card'
+import workModule from './mock/work'
+import UserMoudle from './mock/systemUser'
+
+export function setupProdMockServer() {
+  createProdMockServer([
+    ...userModule,
+    ...tableModule,
+    ...cardModule,
+    ...workModule,
+    ...UserMoudle
+  ]);
+}

+ 3980 - 0
package-lock.json

@@ -0,0 +1,3980 @@
+{
+  "name": "vue-admin-box",
+  "version": "1.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "vue-admin-box",
+      "version": "1.1.0",
+      "dependencies": {
+        "@element-plus/icons": "^0.0.11",
+        "@vueuse/core": "^8.0.0",
+        "axios": "^0.26.1",
+        "echarts": "^5.3.1",
+        "element-plus": "2.3.8",
+        "mockjs": "^1.1.0",
+        "normalize.css": "^8.0.1",
+        "nprogress": "^0.2.0",
+        "vue": "^3.2.31",
+        "vue-router": "4",
+        "vuex": "^4.0.2"
+      },
+      "devDependencies": {
+        "@types/node": "^17.0.21",
+        "@vitejs/plugin-vue": "^2.2.4",
+        "@vue/compiler-sfc": "^3.2.31",
+        "@webxrd/vite-plugin-svg": "^1.0.5",
+        "eslint": "^8.11.0",
+        "sass": "^1.49.9",
+        "unplugin-auto-import": "^0.6.4",
+        "unplugin-vue-components": "^0.18.0",
+        "vite": "^2.8.6",
+        "vite-plugin-mock": "2.9.6",
+        "vue-tsc": "^0.32.1"
+      }
+    },
+    "node_modules/@antfu/utils": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.5.2.tgz",
+      "integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz",
+      "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+      "dependencies": {
+        "@babel/types": "^7.27.1"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz",
+      "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@ctrl/tinycolor": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@element-plus/icons": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons/-/icons-0.0.11.tgz",
+      "integrity": "sha512-iKQXSxXu131Ai+I9Ymtcof9WId7kaXvB1+WRfAfpQCW7UiAMYgdNDqb/u0hgTo2Yq3MwC4MWJnNuTBEpG8r7+A==",
+      "deprecated": "Please use @element-plus/icons-vue instead."
+    },
+    "node_modules/@element-plus/icons-vue": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+      "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@emmetio/abbreviation": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
+      "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==",
+      "dev": true,
+      "dependencies": {
+        "@emmetio/scanner": "^1.0.4"
+      }
+    },
+    "node_modules/@emmetio/css-abbreviation": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmmirror.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz",
+      "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==",
+      "dev": true,
+      "dependencies": {
+        "@emmetio/scanner": "^1.0.4"
+      }
+    },
+    "node_modules/@emmetio/scanner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/@emmetio/scanner/-/scanner-1.0.4.tgz",
+      "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==",
+      "dev": true
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
+      "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+      "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.1",
+      "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.6.0",
+        "globals": "^13.19.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.57.1.tgz",
+      "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@floating-ui/core": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.0.tgz",
+      "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.9"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.0.tgz",
+      "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
+      "dependencies": {
+        "@floating-ui/core": "^1.7.0",
+        "@floating-ui/utils": "^0.2.9"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.9",
+      "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz",
+      "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
+    },
+    "node_modules/@humanwhocodes/config-array": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+      "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+      "deprecated": "Use @eslint/config-array instead",
+      "dev": true,
+      "dependencies": {
+        "@humanwhocodes/object-schema": "^2.0.3",
+        "debug": "^4.3.1",
+        "minimatch": "^3.0.5"
+      },
+      "engines": {
+        "node": ">=10.10.0"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/object-schema": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+      "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+      "deprecated": "Use @eslint/object-schema instead",
+      "dev": true
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@popperjs/core": {
+      "name": "@sxzz/popperjs-es",
+      "version": "2.11.7",
+      "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
+    "node_modules/@rollup/plugin-node-resolve": {
+      "version": "13.3.0",
+      "resolved": "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+      "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+      "dev": true,
+      "dependencies": {
+        "@rollup/pluginutils": "^3.1.0",
+        "@types/resolve": "1.17.1",
+        "deepmerge": "^4.2.2",
+        "is-builtin-module": "^3.1.0",
+        "is-module": "^1.0.0",
+        "resolve": "^1.19.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^2.42.0"
+      }
+    },
+    "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+      "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "0.0.39",
+        "estree-walker": "^1.0.1",
+        "picomatch": "^2.2.2"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^1.20.0||^2.0.0"
+      }
+    },
+    "node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz",
+      "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+      "dev": true
+    },
+    "node_modules/@rollup/pluginutils": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+      "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+      "dev": true,
+      "dependencies": {
+        "estree-walker": "^2.0.1",
+        "picomatch": "^2.2.2"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "0.0.39",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz",
+      "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+      "dev": true
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.17",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.17.tgz",
+      "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ=="
+    },
+    "node_modules/@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "dependencies": {
+        "@types/lodash": "*"
+      }
+    },
+    "node_modules/@types/mockjs": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmmirror.com/@types/mockjs/-/mockjs-1.0.10.tgz",
+      "integrity": "sha512-SXgrhajHG7boLv6oU93CcmdDm0HYRiceuz6b+7z+/2lCJPTWDv0V5YiwFHT2ejE4bQqgSXQiVPQYPWv7LGsK1g==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "17.0.45",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz",
+      "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
+      "dev": true
+    },
+    "node_modules/@types/resolve": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.17.1.tgz",
+      "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/web-bluetooth": {
+      "version": "0.0.14",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
+      "integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
+    },
+    "node_modules/@ungap/structured-clone": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+      "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+      "dev": true
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "2.3.4",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz",
+      "integrity": "sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^2.5.10",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@volar/code-gen": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/code-gen/-/code-gen-0.32.1.tgz",
+      "integrity": "sha512-xk0xJd2NcuQK8SvcQ/Gpvrch3gnd3RjKnHKEFODO6H6UtDpgXQCFprs+j0AuZZWDhVWEShEjuh1jwMmja6qvxg==",
+      "dev": true,
+      "dependencies": {
+        "@volar/shared": "0.32.1",
+        "@volar/source-map": "0.32.1"
+      }
+    },
+    "node_modules/@volar/html2pug": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/html2pug/-/html2pug-0.32.1.tgz",
+      "integrity": "sha512-jWSaiB3W4HFP4wQh91r5kB6161QvSXQjBVE5XiCSFSOXep+FGZ1yyPN03RIznJQE45yv65eJnwW9yxKg+f1YWw==",
+      "deprecated": "WARNING: This project has been renamed to @johnsoncodehk/html2pug. Install using @johnsoncodehk/html2pug instead.",
+      "dev": true,
+      "dependencies": {
+        "domelementtype": "^2.2.0",
+        "domhandler": "^4.3.0",
+        "htmlparser2": "^7.2.0",
+        "pug": "^3.0.2"
+      }
+    },
+    "node_modules/@volar/shared": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/shared/-/shared-0.32.1.tgz",
+      "integrity": "sha512-WROOlXKjtUrnYtx0CXCh/MAKg+Fq0nx5GEKGIpmrFnB/NZku8noOV/e8vS92wTe3JypbJmspU5RwmARcMFRhMg==",
+      "dev": true,
+      "dependencies": {
+        "upath": "^2.0.1",
+        "vscode-html-languageservice": "^4.2.1",
+        "vscode-jsonrpc": "^8.0.0-next.5",
+        "vscode-uri": "^3.0.3"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-0.32.1.tgz",
+      "integrity": "sha512-5QaCf0w/h24MTjk61QntSL7LYDAPPLIqcbYm3eR+WpGy+Gf9sQ1DlFBDCLFmFJfUyZNCE++HCzep9FBJ5K3VGA==",
+      "dev": true,
+      "dependencies": {
+        "@volar/shared": "0.32.1",
+        "vscode-languageserver-textdocument": "^1.0.3"
+      }
+    },
+    "node_modules/@volar/transforms": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/transforms/-/transforms-0.32.1.tgz",
+      "integrity": "sha512-J7DwwB/vjyKrJwnWkR+xM2YTzixbN2G499r56TMe8/uDiZsq7LEGrCNKi6H9k9yeqoL69puq1fQUT3Qrz2Pzwg==",
+      "dev": true,
+      "dependencies": {
+        "@volar/shared": "0.32.1",
+        "vscode-languageserver-types": "^3.17.0-next.6"
+      }
+    },
+    "node_modules/@volar/vue-code-gen": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/@volar/vue-code-gen/-/vue-code-gen-0.32.1.tgz",
+      "integrity": "sha512-JVTmr+hSAgBiLnqewIHn9j9V+28UGigfWVxslBUbz1PwTUeDvVEP7hr3pU3jGWjyj3KjhkVGAFYrpIiGsphyQg==",
+      "deprecated": "WARNING: This project has been renamed to @vue/language-core. Install using @vue/language-core instead.",
+      "dev": true,
+      "dependencies": {
+        "@volar/code-gen": "0.32.1",
+        "@volar/shared": "0.32.1",
+        "@volar/source-map": "0.32.1",
+        "@vue/compiler-core": "^3.2.27",
+        "@vue/compiler-dom": "^3.2.27",
+        "@vue/shared": "^3.2.27",
+        "upath": "^2.0.1"
+      }
+    },
+    "node_modules/@vscode/emmet-helper": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmmirror.com/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz",
+      "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==",
+      "dev": true,
+      "dependencies": {
+        "emmet": "^2.4.3",
+        "jsonc-parser": "^2.3.0",
+        "vscode-languageserver-textdocument": "^1.0.1",
+        "vscode-languageserver-types": "^3.15.1",
+        "vscode-uri": "^3.0.8"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
+      "integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+      "dependencies": {
+        "@babel/parser": "^7.27.2",
+        "@vue/shared": "3.5.14",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
+      "integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.14",
+        "@vue/shared": "3.5.14"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
+      "integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+      "dependencies": {
+        "@babel/parser": "^7.27.2",
+        "@vue/compiler-core": "3.5.14",
+        "@vue/compiler-dom": "3.5.14",
+        "@vue/compiler-ssr": "3.5.14",
+        "@vue/shared": "3.5.14",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.17",
+        "postcss": "^8.5.3",
+        "source-map-js": "^1.2.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
+      "integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.14",
+        "@vue/shared": "3.5.14"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.14.tgz",
+      "integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+      "dependencies": {
+        "@vue/shared": "3.5.14"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
+      "integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.14",
+        "@vue/shared": "3.5.14"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
+      "integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.14",
+        "@vue/runtime-core": "3.5.14",
+        "@vue/shared": "3.5.14",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
+      "integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.14",
+        "@vue/shared": "3.5.14"
+      },
+      "peerDependencies": {
+        "vue": "3.5.14"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.14.tgz",
+      "integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ=="
+    },
+    "node_modules/@vueuse/core": {
+      "version": "8.9.4",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-8.9.4.tgz",
+      "integrity": "sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.14",
+        "@vueuse/metadata": "8.9.4",
+        "@vueuse/shared": "8.9.4",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.0",
+        "vue": "^2.6.0 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/@vueuse/shared": {
+      "version": "8.9.4",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-8.9.4.tgz",
+      "integrity": "sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.1.0",
+        "vue": "^2.6.0 || ^3.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vueuse/metadata": {
+      "version": "8.9.4",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-8.9.4.tgz",
+      "integrity": "sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+      "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+      "dependencies": {
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@webxrd/vite-plugin-svg": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmmirror.com/@webxrd/vite-plugin-svg/-/vite-plugin-svg-1.1.5.tgz",
+      "integrity": "sha512-Zgq3fqhN/FvF7KFPvifmDcYieTRFLl+Zuc1hNZ7UuF7/7kKVCE/sgCZIm1Ed9vF3vCU5rdRWkGMx+Df06wk+Tw==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "^20.11.24",
+        "cors": "^2.8.5",
+        "etag": "^1.8.1",
+        "fast-glob": "^3.2.11",
+        "user": "^0.0.0"
+      }
+    },
+    "node_modules/@webxrd/vite-plugin-svg/node_modules/@types/node": {
+      "version": "20.17.50",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.17.50.tgz",
+      "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~6.19.2"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.14.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+      "dev": true
+    },
+    "node_modules/assert-never": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/assert-never/-/assert-never-1.4.0.tgz",
+      "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==",
+      "dev": true
+    },
+    "node_modules/async-validator": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
+    },
+    "node_modules/axios": {
+      "version": "0.26.1",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz",
+      "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+      "dependencies": {
+        "follow-redirects": "^1.14.8"
+      }
+    },
+    "node_modules/babel-walk": {
+      "version": "3.0.0-canary-5",
+      "resolved": "https://registry.npmmirror.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
+      "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.9.6"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/builtin-modules": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz",
+      "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dev": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "dev": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/character-parser": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/character-parser/-/character-parser-2.2.0.tgz",
+      "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==",
+      "dev": true,
+      "dependencies": {
+        "is-regex": "^1.0.3"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chokidar/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/commander": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.0.tgz",
+      "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
+      "engines": {
+        "node": ">=20"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/connect": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmmirror.com/connect/-/connect-3.7.0.tgz",
+      "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+      "dev": true,
+      "dependencies": {
+        "debug": "2.6.9",
+        "finalhandler": "1.1.2",
+        "parseurl": "~1.3.3",
+        "utils-merge": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/connect/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/connect/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true
+    },
+    "node_modules/constantinople": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/constantinople/-/constantinople-4.0.1.tgz",
+      "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.6.0",
+        "@babel/types": "^7.6.1"
+      }
+    },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "dev": true,
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+    },
+    "node_modules/debug": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
+      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/deepmerge": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz",
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/doctypes": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/doctypes/-/doctypes-1.1.0.tgz",
+      "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==",
+      "dev": true
+    },
+    "node_modules/dom-serializer": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
+      "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+      "dev": true,
+      "dependencies": {
+        "domelementtype": "^2.0.1",
+        "domhandler": "^4.2.0",
+        "entities": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+      }
+    },
+    "node_modules/dom-serializer/node_modules/entities": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz",
+      "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/domelementtype": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz",
+      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fb55"
+        }
+      ]
+    },
+    "node_modules/domhandler": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz",
+      "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+      "dev": true,
+      "dependencies": {
+        "domelementtype": "^2.2.0"
+      },
+      "engines": {
+        "node": ">= 4"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/domhandler?sponsor=1"
+      }
+    },
+    "node_modules/domutils": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
+      "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+      "dev": true,
+      "dependencies": {
+        "dom-serializer": "^1.0.1",
+        "domelementtype": "^2.2.0",
+        "domhandler": "^4.2.0"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/domutils?sponsor=1"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dev": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "dev": true
+    },
+    "node_modules/element-plus": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.8.tgz",
+      "integrity": "sha512-yHQR0/tG2LvPkpGUt7Te/hPmP2XW/BytBNUbx+EFO54VnGCOE3upmQcVffNp1PLgwg9sthYDXontUWpnpmLPJw==",
+      "dependencies": {
+        "@ctrl/tinycolor": "^3.4.1",
+        "@element-plus/icons-vue": "^2.0.6",
+        "@floating-ui/dom": "^1.0.1",
+        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+        "@types/lodash": "^4.14.182",
+        "@types/lodash-es": "^4.17.6",
+        "@vueuse/core": "^9.1.0",
+        "async-validator": "^4.2.5",
+        "dayjs": "^1.11.3",
+        "escape-html": "^1.0.3",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2",
+        "memoize-one": "^6.0.0",
+        "normalize-wheel-es": "^1.2.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/element-plus/node_modules/@types/web-bluetooth": {
+      "version": "0.0.16",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+      "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
+    },
+    "node_modules/element-plus/node_modules/@vueuse/core": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+      "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.16",
+        "@vueuse/metadata": "9.13.0",
+        "@vueuse/shared": "9.13.0",
+        "vue-demi": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/element-plus/node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/element-plus/node_modules/@vueuse/metadata": {
+      "version": "9.13.0",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+      "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/emmet": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmmirror.com/emmet/-/emmet-2.4.11.tgz",
+      "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==",
+      "dev": true,
+      "workspaces": [
+        "./packages/scanner",
+        "./packages/abbreviation",
+        "./packages/css-abbreviation",
+        "./"
+      ],
+      "dependencies": {
+        "@emmetio/abbreviation": "^2.3.3",
+        "@emmetio/css-abbreviation": "^2.1.8"
+      }
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dev": true,
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.14.54.tgz",
+      "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/linux-loong64": "0.14.54",
+        "esbuild-android-64": "0.14.54",
+        "esbuild-android-arm64": "0.14.54",
+        "esbuild-darwin-64": "0.14.54",
+        "esbuild-darwin-arm64": "0.14.54",
+        "esbuild-freebsd-64": "0.14.54",
+        "esbuild-freebsd-arm64": "0.14.54",
+        "esbuild-linux-32": "0.14.54",
+        "esbuild-linux-64": "0.14.54",
+        "esbuild-linux-arm": "0.14.54",
+        "esbuild-linux-arm64": "0.14.54",
+        "esbuild-linux-mips64le": "0.14.54",
+        "esbuild-linux-ppc64le": "0.14.54",
+        "esbuild-linux-riscv64": "0.14.54",
+        "esbuild-linux-s390x": "0.14.54",
+        "esbuild-netbsd-64": "0.14.54",
+        "esbuild-openbsd-64": "0.14.54",
+        "esbuild-sunos-64": "0.14.54",
+        "esbuild-windows-32": "0.14.54",
+        "esbuild-windows-64": "0.14.54",
+        "esbuild-windows-arm64": "0.14.54"
+      }
+    },
+    "node_modules/esbuild-android-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
+      "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-android-arm64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
+      "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-darwin-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
+      "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-darwin-arm64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
+      "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-freebsd-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
+      "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-freebsd-arm64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
+      "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-32": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
+      "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
+      "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-arm": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
+      "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-arm64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
+      "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-mips64le": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
+      "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-ppc64le": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
+      "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-riscv64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
+      "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-linux-s390x": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
+      "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-netbsd-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
+      "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-openbsd-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
+      "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-sunos-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
+      "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-32": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
+      "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
+      "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/esbuild-windows-arm64": {
+      "version": "0.14.54",
+      "resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
+      "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "8.57.1",
+      "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz",
+      "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+      "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.4",
+        "@eslint/js": "8.57.1",
+        "@humanwhocodes/config-array": "^0.13.0",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@nodelib/fs.walk": "^1.2.8",
+        "@ungap/structured-clone": "^1.2.0",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.3",
+        "espree": "^9.6.1",
+        "esquery": "^1.4.2",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "globals": "^13.19.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "is-path-inside": "^3.0.3",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "strip-ansi": "^6.0.1",
+        "text-table": "^0.2.0"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz",
+      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/espree": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz",
+      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.9.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
+      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.8"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-glob/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.19.1",
+      "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
+      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^3.0.4"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dev": true,
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/finalhandler/node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/finalhandler/node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "dev": true
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz",
+      "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.3",
+        "rimraf": "^3.0.2"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
+      "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dev": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dev": true,
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "deprecated": "Glob versions prior to v9 are no longer supported",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/globals": {
+      "version": "13.24.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz",
+      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dev": true,
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dev": true,
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/htmlparser2": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-7.2.0.tgz",
+      "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
+      "dev": true,
+      "funding": [
+        "https://github.com/fb55/htmlparser2?sponsor=1",
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fb55"
+        }
+      ],
+      "dependencies": {
+        "domelementtype": "^2.0.1",
+        "domhandler": "^4.2.2",
+        "domutils": "^2.8.0",
+        "entities": "^3.0.1"
+      }
+    },
+    "node_modules/htmlparser2/node_modules/entities": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz",
+      "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/immutable": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.7.tgz",
+      "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
+      "dev": true
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
+      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+      "dev": true,
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-builtin-module": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+      "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+      "dev": true,
+      "dependencies": {
+        "builtin-modules": "^3.3.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dev": true,
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-expression": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/is-expression/-/is-expression-4.0.0.tgz",
+      "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^7.1.1",
+        "object-assign": "^4.1.1"
+      }
+    },
+    "node_modules/is-expression/node_modules/acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz",
+      "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+      "dev": true
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-path-inside": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz",
+      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-promise": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz",
+      "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+      "dev": true
+    },
+    "node_modules/is-regex": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
+      "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+      "dev": true,
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "gopd": "^1.2.0",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-stringify": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/js-stringify/-/js-stringify-1.0.2.tgz",
+      "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/jsonc-parser": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
+      "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
+      "dev": true
+    },
+    "node_modules/jstransformer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/jstransformer/-/jstransformer-1.0.0.tgz",
+      "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==",
+      "dev": true,
+      "dependencies": {
+        "is-promise": "^2.0.0",
+        "promise": "^7.0.1"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/local-pkg": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz",
+      "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
+    "node_modules/lodash-unified": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+      "peerDependencies": {
+        "@types/lodash-es": "*",
+        "lodash": "*",
+        "lodash-es": "*"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.17",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/memoize-one": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/mockjs": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/mockjs/-/mockjs-1.1.0.tgz",
+      "integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
+      "dependencies": {
+        "commander": "*"
+      },
+      "bin": {
+        "random": "bin/random"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/normalize-wheel-es": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
+    },
+    "node_modules/normalize.css": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmmirror.com/normalize.css/-/normalize.css-8.0.1.tgz",
+      "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
+    },
+    "node_modules/nprogress": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
+      "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+      "dev": true,
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "node_modules/path-to-regexp": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+      "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+      "dev": true
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.3",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.8",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/promise": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmmirror.com/promise/-/promise-7.3.1.tgz",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "dev": true,
+      "dependencies": {
+        "asap": "~2.0.3"
+      }
+    },
+    "node_modules/pug": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/pug/-/pug-3.0.3.tgz",
+      "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==",
+      "dev": true,
+      "dependencies": {
+        "pug-code-gen": "^3.0.3",
+        "pug-filters": "^4.0.0",
+        "pug-lexer": "^5.0.1",
+        "pug-linker": "^4.0.0",
+        "pug-load": "^3.0.0",
+        "pug-parser": "^6.0.0",
+        "pug-runtime": "^3.0.1",
+        "pug-strip-comments": "^2.0.0"
+      }
+    },
+    "node_modules/pug-attrs": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-attrs/-/pug-attrs-3.0.0.tgz",
+      "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
+      "dev": true,
+      "dependencies": {
+        "constantinople": "^4.0.1",
+        "js-stringify": "^1.0.2",
+        "pug-runtime": "^3.0.0"
+      }
+    },
+    "node_modules/pug-code-gen": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/pug-code-gen/-/pug-code-gen-3.0.3.tgz",
+      "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==",
+      "dev": true,
+      "dependencies": {
+        "constantinople": "^4.0.1",
+        "doctypes": "^1.1.0",
+        "js-stringify": "^1.0.2",
+        "pug-attrs": "^3.0.0",
+        "pug-error": "^2.1.0",
+        "pug-runtime": "^3.0.1",
+        "void-elements": "^3.1.0",
+        "with": "^7.0.0"
+      }
+    },
+    "node_modules/pug-error": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/pug-error/-/pug-error-2.1.0.tgz",
+      "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
+      "dev": true
+    },
+    "node_modules/pug-filters": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-filters/-/pug-filters-4.0.0.tgz",
+      "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==",
+      "dev": true,
+      "dependencies": {
+        "constantinople": "^4.0.1",
+        "jstransformer": "1.0.0",
+        "pug-error": "^2.0.0",
+        "pug-walk": "^2.0.0",
+        "resolve": "^1.15.1"
+      }
+    },
+    "node_modules/pug-lexer": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/pug-lexer/-/pug-lexer-5.0.1.tgz",
+      "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
+      "dev": true,
+      "dependencies": {
+        "character-parser": "^2.2.0",
+        "is-expression": "^4.0.0",
+        "pug-error": "^2.0.0"
+      }
+    },
+    "node_modules/pug-linker": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-linker/-/pug-linker-4.0.0.tgz",
+      "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==",
+      "dev": true,
+      "dependencies": {
+        "pug-error": "^2.0.0",
+        "pug-walk": "^2.0.0"
+      }
+    },
+    "node_modules/pug-load": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-load/-/pug-load-3.0.0.tgz",
+      "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==",
+      "dev": true,
+      "dependencies": {
+        "object-assign": "^4.1.1",
+        "pug-walk": "^2.0.0"
+      }
+    },
+    "node_modules/pug-parser": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-parser/-/pug-parser-6.0.0.tgz",
+      "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
+      "dev": true,
+      "dependencies": {
+        "pug-error": "^2.0.0",
+        "token-stream": "1.0.0"
+      }
+    },
+    "node_modules/pug-runtime": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/pug-runtime/-/pug-runtime-3.0.1.tgz",
+      "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==",
+      "dev": true
+    },
+    "node_modules/pug-strip-comments": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz",
+      "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==",
+      "dev": true,
+      "dependencies": {
+        "pug-error": "^2.0.0"
+      }
+    },
+    "node_modules/pug-walk": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/pug-walk/-/pug-walk-2.0.0.tgz",
+      "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==",
+      "dev": true
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.10",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
+      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.16.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "deprecated": "Rimraf versions prior to v4 are no longer supported",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "2.77.3",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.77.3.tgz",
+      "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
+      "dev": true,
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.49.9",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.49.9.tgz",
+      "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=3.0.0 <4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+      "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+      "dev": true
+    },
+    "node_modules/statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/token-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/token-stream/-/token-stream-1.0.0.tgz",
+      "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==",
+      "dev": true
+    },
+    "node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.8.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz",
+      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+      "devOptional": true,
+      "peer": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.19.8",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+      "dev": true
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/unplugin": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-0.4.0.tgz",
+      "integrity": "sha512-4ScITEmzlz1iZW3tkz+3L1V5k/xMQ6kjgm4lEXKxH0ozd8/OUWfiSA7RMRyrawsvq/t50JIzPpp1UyuSL/AXkA==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.5.3",
+        "webpack-virtual-modules": "^0.4.3"
+      },
+      "peerDependencies": {
+        "esbuild": ">=0.13",
+        "rollup": "^2.50.0",
+        "vite": "^2.3.0",
+        "webpack": "4 || 5"
+      },
+      "peerDependenciesMeta": {
+        "esbuild": {
+          "optional": true
+        },
+        "rollup": {
+          "optional": true
+        },
+        "vite": {
+          "optional": true
+        },
+        "webpack": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-auto-import": {
+      "version": "0.6.9",
+      "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.6.9.tgz",
+      "integrity": "sha512-IqgT7AoRrNQwNhiF/wDH3sMEDX8SqCYBEgJzwdg5441b5aiC5VwZz0J0wYqkaKu89YkZE9DG6rQ2JpFfZv1iiQ==",
+      "dev": true,
+      "dependencies": {
+        "@antfu/utils": "^0.5.0",
+        "@rollup/pluginutils": "^4.2.0",
+        "local-pkg": "^0.4.1",
+        "magic-string": "^0.26.1",
+        "resolve": "^1.22.0",
+        "unplugin": "^0.4.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vueuse/core": "*"
+      },
+      "peerDependenciesMeta": {
+        "@vueuse/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-auto-import/node_modules/magic-string": {
+      "version": "0.26.7",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.26.7.tgz",
+      "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
+      "dev": true,
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/unplugin-vue-components": {
+      "version": "0.18.5",
+      "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.18.5.tgz",
+      "integrity": "sha512-VPA6z/4pcKRDYtWu1H+FIpV0MADlFKG3q7YMVFzNFC3EhMVZ4WuBJ76490oKyauguNw1T1obLCuxNU9JzJ0oAQ==",
+      "dev": true,
+      "dependencies": {
+        "@antfu/utils": "^0.5.0",
+        "@rollup/pluginutils": "^4.2.0",
+        "chokidar": "^3.5.3",
+        "debug": "^4.3.3",
+        "fast-glob": "^3.2.11",
+        "local-pkg": "^0.4.1",
+        "magic-string": "^0.26.1",
+        "minimatch": "^5.0.1",
+        "resolve": "^1.22.0",
+        "unplugin": "^0.4.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@babel/parser": "^7.15.8",
+        "@babel/traverse": "^7.15.4",
+        "vue": "2 || 3"
+      },
+      "peerDependenciesMeta": {
+        "@babel/parser": {
+          "optional": true
+        },
+        "@babel/traverse": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-vue-components/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/unplugin-vue-components/node_modules/magic-string": {
+      "version": "0.26.7",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.26.7.tgz",
+      "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==",
+      "dev": true,
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/unplugin-vue-components/node_modules/minimatch": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/upath": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/upath/-/upath-2.0.1.tgz",
+      "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==",
+      "dev": true,
+      "engines": {
+        "node": ">=4",
+        "yarn": "*"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/user": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmmirror.com/user/-/user-0.0.0.tgz",
+      "integrity": "sha512-eRNM5isOvr6aEFAGi1CqAkmLkYxW2NJ5ThhbD+6IJXYKM1mo7Gtxx4mQIveHz/5K3p/SVnlW9k17ETn+QAu8IQ==",
+      "dev": true
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/vite": {
+      "version": "2.9.18",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-2.9.18.tgz",
+      "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.14.27",
+        "postcss": "^8.4.13",
+        "resolve": "^1.22.0",
+        "rollup": ">=2.59.0 <2.78.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": ">=12.2.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      },
+      "peerDependencies": {
+        "less": "*",
+        "sass": "*",
+        "stylus": "*"
+      },
+      "peerDependenciesMeta": {
+        "less": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite-plugin-mock": {
+      "version": "2.9.6",
+      "resolved": "https://registry.npmmirror.com/vite-plugin-mock/-/vite-plugin-mock-2.9.6.tgz",
+      "integrity": "sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==",
+      "dev": true,
+      "dependencies": {
+        "@rollup/plugin-node-resolve": "^13.0.4",
+        "@types/mockjs": "^1.0.4",
+        "chalk": "^4.1.2",
+        "chokidar": "^3.5.2",
+        "connect": "^3.7.0",
+        "debug": "^4.3.2",
+        "esbuild": "0.11.3",
+        "fast-glob": "^3.2.7",
+        "path-to-regexp": "^6.2.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "mockjs": ">=1.1.0",
+        "vite": ">=2.0.0"
+      }
+    },
+    "node_modules/vite-plugin-mock/node_modules/esbuild": {
+      "version": "0.11.3",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.11.3.tgz",
+      "integrity": "sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      }
+    },
+    "node_modules/void-elements": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
+      "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/vscode-css-languageservice": {
+      "version": "5.4.2",
+      "resolved": "https://registry.npmmirror.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz",
+      "integrity": "sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg==",
+      "dev": true,
+      "dependencies": {
+        "vscode-languageserver-textdocument": "^1.0.4",
+        "vscode-languageserver-types": "^3.16.0",
+        "vscode-nls": "^5.0.0",
+        "vscode-uri": "^3.0.3"
+      }
+    },
+    "node_modules/vscode-html-languageservice": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmmirror.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.5.tgz",
+      "integrity": "sha512-dbr10KHabB9EaK8lI0XZW7SqOsTfrNyT3Nuj0GoPi4LjGKUmMiLtsqzfedIzRTzqY+w0FiLdh0/kQrnQ0tLxrw==",
+      "dev": true,
+      "dependencies": {
+        "vscode-languageserver-textdocument": "^1.0.4",
+        "vscode-languageserver-types": "^3.16.0",
+        "vscode-nls": "^5.0.0",
+        "vscode-uri": "^3.0.3"
+      }
+    },
+    "node_modules/vscode-json-languageservice": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmmirror.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz",
+      "integrity": "sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==",
+      "dev": true,
+      "dependencies": {
+        "jsonc-parser": "^3.0.0",
+        "vscode-languageserver-textdocument": "^1.0.3",
+        "vscode-languageserver-types": "^3.16.0",
+        "vscode-nls": "^5.0.0",
+        "vscode-uri": "^3.0.3"
+      }
+    },
+    "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
+      "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
+      "dev": true
+    },
+    "node_modules/vscode-jsonrpc": {
+      "version": "8.2.1",
+      "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz",
+      "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/vscode-languageserver-protocol": {
+      "version": "3.17.5",
+      "resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+      "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+      "dev": true,
+      "dependencies": {
+        "vscode-jsonrpc": "8.2.0",
+        "vscode-languageserver-types": "3.17.5"
+      }
+    },
+    "node_modules/vscode-languageserver-protocol/node_modules/vscode-jsonrpc": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+      "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/vscode-languageserver-textdocument": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+      "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+      "dev": true
+    },
+    "node_modules/vscode-languageserver-types": {
+      "version": "3.17.5",
+      "resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+      "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+      "dev": true
+    },
+    "node_modules/vscode-nls": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/vscode-nls/-/vscode-nls-5.2.0.tgz",
+      "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==",
+      "dev": true
+    },
+    "node_modules/vscode-pug-languageservice": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/vscode-pug-languageservice/-/vscode-pug-languageservice-0.32.1.tgz",
+      "integrity": "sha512-kPmucTsCuEKk4f/2T549htcCxfkxReGkQG8j+nA0vvVhVGrjbQOiTgPk+KEA75PmrO6353p5ACXQiG0zI/O+AA==",
+      "deprecated": "WARNING: This project has been renamed to @volar/pug-language-service. Install using @volar/pug-language-service instead.",
+      "dev": true,
+      "dependencies": {
+        "@volar/code-gen": "0.32.1",
+        "@volar/shared": "0.32.1",
+        "@volar/source-map": "0.32.1",
+        "@volar/transforms": "0.32.1",
+        "pug-lexer": "^5.0.1",
+        "pug-parser": "^6.0.0",
+        "vscode-languageserver-textdocument": "^1.0.3",
+        "vscode-languageserver-types": "^3.17.0-next.6"
+      }
+    },
+    "node_modules/vscode-typescript-languageservice": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/vscode-typescript-languageservice/-/vscode-typescript-languageservice-0.32.1.tgz",
+      "integrity": "sha512-7fFYTadMF/LkoNKmawipNefFPu3MCloWqiWFgbUKsjvvAAER/dY0W7Zml0/Qaj0eXriLoe0KabA3nWTiCxXAMQ==",
+      "deprecated": "WARNING: This project has been renamed to @volar/typescript-language-service. Install using @volar/typescript-language-service instead.",
+      "dev": true,
+      "dependencies": {
+        "@volar/shared": "0.32.1",
+        "semver": "^7.3.5",
+        "upath": "^2.0.1",
+        "vscode-languageserver-protocol": "^3.17.0-next.12",
+        "vscode-languageserver-textdocument": "^1.0.3",
+        "vscode-nls": "^5.0.0"
+      }
+    },
+    "node_modules/vscode-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
+      "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+      "dev": true
+    },
+    "node_modules/vscode-vue-languageservice": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/vscode-vue-languageservice/-/vscode-vue-languageservice-0.32.1.tgz",
+      "integrity": "sha512-tme8aDa/ScQnACIvMWGOoHQsoDZAsp2qa+EGXEtqpcyPGBmrVlu0R/TAzRl43gVe6tv3qVPOv3s0XdK/1bMPBQ==",
+      "deprecated": "WARNING: This project has been renamed to @volar/vue-language-service. Install using @volar/vue-language-service instead.",
+      "dev": true,
+      "dependencies": {
+        "@volar/code-gen": "0.32.1",
+        "@volar/html2pug": "0.32.1",
+        "@volar/shared": "0.32.1",
+        "@volar/source-map": "0.32.1",
+        "@volar/transforms": "0.32.1",
+        "@volar/vue-code-gen": "0.32.1",
+        "@vscode/emmet-helper": "^2.8.3",
+        "@vue/reactivity": "^3.2.27",
+        "@vue/shared": "^3.2.27",
+        "upath": "^2.0.1",
+        "vscode-css-languageservice": "^5.1.9",
+        "vscode-html-languageservice": "^4.2.1",
+        "vscode-json-languageservice": "^4.1.10",
+        "vscode-languageserver-protocol": "^3.17.0-next.12",
+        "vscode-languageserver-textdocument": "^1.0.3",
+        "vscode-pug-languageservice": "0.32.1",
+        "vscode-typescript-languageservice": "0.32.1"
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.14",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.14.tgz",
+      "integrity": "sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.14",
+        "@vue/compiler-sfc": "3.5.14",
+        "@vue/runtime-dom": "3.5.14",
+        "@vue/server-renderer": "3.5.14",
+        "@vue/shared": "3.5.14"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
+      "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/vue-tsc": {
+      "version": "0.32.1",
+      "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-0.32.1.tgz",
+      "integrity": "sha512-HR34cFr14GDpV9WMX0kMxIAvieg735Q5sQ67MkIBEXtjuR2Yo5y43noiMMjoHZ6YqcjidXlCFbBiJrplLX/W7w==",
+      "dev": true,
+      "dependencies": {
+        "@volar/shared": "0.32.1",
+        "vscode-vue-languageservice": "0.32.1"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      }
+    },
+    "node_modules/vuex": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.1.0.tgz",
+      "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.0.0-beta.11"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/webpack-virtual-modules": {
+      "version": "0.4.6",
+      "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz",
+      "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==",
+      "dev": true
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/with": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmmirror.com/with/-/with-7.0.2.tgz",
+      "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.9.6",
+        "@babel/types": "^7.9.6",
+        "assert-never": "^1.2.1",
+        "babel-walk": "3.0.0-canary-5"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    }
+  }
+}

+ 37 - 0
package.json

@@ -0,0 +1,37 @@
+{
+  "name": "vue-admin-box",
+  "version": "1.1.0",
+  "scripts": {
+    "dev": "vite",
+    "start": "vite",
+    "build": "vite build --mode=production",
+    "build:stag": "vite build --mode=staging",
+    "serve": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons": "^0.0.11",
+    "@vueuse/core": "^8.0.0",
+    "axios": "^0.26.1",
+    "echarts": "^5.3.1",
+    "element-plus": "2.3.8",
+    "mockjs": "^1.1.0",
+    "normalize.css": "^8.0.1",
+    "nprogress": "^0.2.0",
+    "vue": "^3.2.31",
+    "vue-router": "4",
+    "vuex": "^4.0.2"
+  },
+  "devDependencies": {
+    "@types/node": "^17.0.21",
+    "@vitejs/plugin-vue": "^2.2.4",
+    "@vue/compiler-sfc": "^3.2.31",
+    "@webxrd/vite-plugin-svg": "^1.0.5",
+    "eslint": "^8.11.0",
+    "sass": "^1.49.9",
+    "unplugin-auto-import": "^0.6.4",
+    "unplugin-vue-components": "^0.18.0",
+    "vite": "^2.8.6",
+    "vite-plugin-mock": "2.9.6",
+    "vue-tsc": "^0.32.1"
+  }
+}

TEMPAT SAMPAH
public/favicon.ico


+ 42 - 0
src/App.vue

@@ -0,0 +1,42 @@
+<template>
+  <el-config-provider :size="size">
+    <router-view></router-view>
+  </el-config-provider>
+</template>
+
+<script lang="js">
+  import { defineComponent, computed, onMounted } from 'vue'
+  import { useStore } from 'vuex'
+  import { useGetDictList } from '@/hooks/useGetDictList.js'
+  export default defineComponent({
+    name: 'App',
+    setup() {
+      const store = useStore()
+      const size = computed(() => store.state.app.elementSize)
+      const { loadDictData } = useGetDictList();
+
+      onMounted(() => {
+        // 获取公共字典数据
+        if (store.state.user.token) {
+          loadDictData();
+        }
+      });
+
+      return {
+        size,
+      }
+    }
+  })
+</script>
+
+<style>
+  #app {
+    font-family: Avenir, Helvetica, Arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    text-align: center;
+    color: #2c3e50;
+    width: 100%;
+    height: 100vh;
+  }
+</style>

+ 10 - 0
src/api/common.js

@@ -0,0 +1,10 @@
+import request from '@/utils/system/request'
+
+// 查询字典数据列表
+export function getCommonDictList(params) {
+    return request({
+        url: '/agent-service/common/dictList',
+        method: 'get',
+        params
+    })
+}

+ 51 - 0
src/api/login.js

@@ -0,0 +1,51 @@
+import request from '@/utils/system/request'
+
+
+// 根据当前时间生成唯一UUID用于本次登录验证码
+export function getCaptchaImg(params) {
+    return request({
+        url: '/yt-oauth/oauth/captcha.jpg',
+        method: 'get',
+        responseType: 'blob',
+        params
+    })
+}
+
+
+// 登录 使用 x-www-form-urlencoded
+export function login(parameter) {
+    return request({
+        url: '/yt-oauth/oauth/token',
+        method: 'post',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Authorization': 'Basic eXQtcGxhdGZvcm06eXQtc2VjcmV0LTIwMjU='
+        },
+        data: toFormUrlEncoded(parameter)
+    })
+}
+
+// 退出登录
+export function logout(data) {
+    return request({
+        url: '/yt-oauth/oauth/logout',
+        method: 'post',
+        data
+    })
+}
+
+// 获取当前登录用户
+export function getUserInfo() {
+    return request({
+        url: '/agent-service/ytUser/curUser',
+        method: 'get',
+    })
+}
+
+// 自定义序列化函数 转成 x-www-form-urlencoded 格式
+function toFormUrlEncoded(obj) {
+    return Object.keys(obj)
+        .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
+        .join('&');
+}
+

+ 91 - 0
src/api/riskModule.js

@@ -0,0 +1,91 @@
+import request from '@/utils/system/request'
+
+// 平台或渠道封禁用户
+export function riskConfigList(data) {
+    return request({
+        url: '/agent-service/risk/configList',
+        method: 'post',
+        data
+    })
+}
+
+// 查询风控用户记录 - 风控日志表
+export function riskBannedList(data) {
+    return request({
+        url: '/agent-service/risk/bannedList',
+        method: 'post',
+        data
+    })
+}
+
+// 查询解封记录-解封日志表
+export function riskDeblockingList(data) {
+    return request({
+        url: '/agent-service/risk/deblockingList',
+        method: 'post',
+        data
+    })
+}
+
+// 修改用户风控状态
+export function riskChangeUserStatus(data) {
+    return request({
+        url: '/agent-service/risk/changeUserStatus',
+        method: 'post',
+        data
+    })
+}
+
+// 风控配置关联应用
+export function riskRelativeApps(data) {
+    return request({
+        url: '/agent-service/risk/relativeApp',
+        method: 'post',
+        data
+    })
+}
+
+// 保存风控配置
+export function riskSaveConfig(data) {
+    return request({
+        url: '/agent-service/risk/saveConfig',
+        method: 'post',
+        data
+    })
+}
+
+// 保存风控配置
+export function riskUpdateConfig(data) {
+    return request({
+        url: '/agent-service/risk/updateConfig',
+        method: 'post',
+        data
+    })
+}
+
+// 查询配置字段选项
+export function riskOptions(params) {
+    return request({
+        url: '/agent-service/risk/options',
+        method: 'get',
+        params
+    })
+}
+
+// 启用或关闭风控配置
+export function riskEnabledConfig(params) {
+    return request({
+        url: '/agent-service/risk/enabledConfig',
+        method: 'get',
+        params
+    })
+}
+// 风控配置模板详情
+export function riskTemplateView(params) {
+    return request({
+        url: '/agent-service/risk/templateView',
+        method: 'get',
+        params
+    })
+}
+

+ 49 - 0
src/api/user.js

@@ -0,0 +1,49 @@
+import request from '@/utils/system/request'
+
+/** 登录api */
+export function loginApi(data) {
+  return request({
+    url: '/user/login',
+    method: 'post',
+    baseURL: '/mock',
+    data
+  })
+}
+
+/** 获取用户信息Api */
+export function getInfoApi(data) {
+  return request({
+    url: '/user/info',
+    method: 'post',
+    baseURL: '/mock',
+    data
+  })
+}
+
+/** 退出登录Api */
+export function loginOutApi() {
+  return request({
+    url: '/user/out',
+    method: 'post',
+    baseURL: '/mock'
+  })
+}
+
+/** 获取用户信息Api */
+export function passwordChange(data) {
+  return request({
+    url: '/user/passwordChange',
+    method: 'post',
+    baseURL: '/mock',
+    data
+  })
+}
+
+/** 获取登录后需要展示的菜单 */
+export function getMenuApi() {
+  return request({
+    url: '/menu/list',
+    method: 'post',
+    baseURL: '/mock'
+  })
+}

+ 46 - 0
src/api/userModule.js

@@ -0,0 +1,46 @@
+import request from '@/utils/system/request'
+
+// 查询App用户列表
+export function getUserList(data) {
+    return request({
+        url: '/agent-service/appUser/getUserList',
+        method: 'post',
+        data
+    })
+}
+
+// 查询用户广告数据统计列表
+export function getStaticList(params) {
+    return request({
+        url: '/agent-service/appUser/staticList',
+        method: 'get',
+        params
+    })
+}
+
+// 平台或渠道封禁用户
+export function riskBannedUser(data) {
+    return request({
+        url: '/agent-service/risk/bannedUser',
+        method: 'post',
+        data
+    })
+}
+
+// 锁定用户禁止用户登录
+export function riskLockUser(params) {
+    return request({
+        url: '/agent-service/risk/lockUser',
+        method: 'get',
+        params
+    })
+}
+
+// 查询渠道启用的应用列表
+export function getEnabledList(params) {
+    return request({
+        url: '/agent-service/app/getEnabledList',
+        method: 'get',
+        params
+    })
+}

TEMPAT SAMPAH
src/assets/images/401.gif


TEMPAT SAMPAH
src/assets/images/404.png


TEMPAT SAMPAH
src/assets/images/404_cloud.png


TEMPAT SAMPAH
src/assets/login/bg.png


TEMPAT SAMPAH
src/assets/login/left.jpg


TEMPAT SAMPAH
src/assets/logo.png


+ 284 - 0
src/assets/style/common.scss

@@ -0,0 +1,284 @@
+@use "./transition.scss";
+@use "@/theme/index.scss";
+.layout-container {
+  background-color: var(--system-container-main-background);
+  width: calc(100% - 60px);
+  height: calc(100% - 60px);
+  padding: 15px;
+  margin: 15px;
+  display: flex;
+  flex-direction: column;
+  overflow-y: auto;
+  // overflow: hidden;
+  &-form {
+    display: flex;
+    flex-wrap: wrap;
+    padding: 0 15px 0 15px;
+    .el-date-editor.el-input {
+      width: 100%;
+    }
+
+    &-handle {
+      display: flex;
+      justify-content: flex-start;
+      flex: 1 1 100%;
+
+      .export-excel-btn {
+        margin-left: 15px;
+      }
+    }
+
+    &-search {
+      display: flex;
+      justify-content: flex-end;
+      flex: 1 1 100%;
+
+      .search-btn {
+        margin-left: 15px;
+      }
+    }
+
+    .el-form-item {
+      margin-bottom: 0;
+      margin-top: 15px;
+      flex: 0 0 calc(25% - 100px); /* 修改 */
+
+      &:nth-child(4n) {
+        margin-right: 0; /* 新增 */
+        flex: 0 0 calc(25% - 100px); /* 修改 */
+      }
+
+      &:not(:nth-child(4n)) {
+        margin-right: 133px; /* 新增 */
+      }
+    }
+    .is_button {
+      .el-form-item__content {
+        margin-left: 0 !important;
+      }
+    }
+  }
+  &-tabs {
+    margin: 20px 15px 0 15px;
+    .el-tabs__header {
+      margin: 0;
+    }
+  }
+  &-table {
+    flex: 1;
+    height: 100%;
+    padding: 15px;
+    overflow: auto;
+  }
+}
+.flex-box {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  padding: 15px;
+  box-sizing: border-box;
+}
+.flex {
+  display: flex;
+  align-items: center;
+}
+.flex1 {
+  display: flex;
+  justify-content: space-between; /* 让子元素平均分布 */
+}
+.flexItem1 {
+  flex: 1;
+}
+.flexEnd {
+  display: flex;
+  justify-content: flex-end;
+}
+.center {
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+}
+a {
+  text-decoration: none;
+}
+
+/** element-plus **/
+.el-icon {
+  text-align: center;
+}
+
+/** 用于提示信息 **/
+.my-tip {
+  background-color: #f1f1f1;
+  padding: 5px 10px;
+  text-align: left;
+  border-radius: 4px;
+}
+.system-scrollbar {
+  &::-webkit-scrollbar {
+    display: none;
+    width: 6px;
+  }
+  &::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    background: rgba(144, 147, 153, 0.3);
+  }
+  &:hover {
+    &::-webkit-scrollbar {
+      display: block;
+    }
+    &::-webkit-scrollbar-thumb {
+      border-radius: 10px;
+      background: rgba(144, 147, 153, 0.3);
+      &:hover {
+        background: rgba(144, 147, 153, 0.5);
+      }
+    }
+  }
+}
+
+.form-container {
+  background-color: var(--system-container-main-background);
+  width: calc(100% - 30px);
+  height: calc(100% - 30px);
+  margin: 15px;
+  display: flex;
+  flex-direction: column;
+  overflow-y: auto;
+  &-form {
+    display: flex;
+    flex-wrap: wrap;
+    padding: 0 15px 0 15px;
+    .el-date-editor.el-input {
+      width: 100%;
+    }
+
+    &-handle {
+      display: flex;
+      justify-content: flex-start;
+      flex: 1 1 100%;
+
+      .export-excel-btn {
+        margin-left: 15px;
+      }
+    }
+
+    &-search {
+      display: flex;
+      justify-content: flex-end;
+      flex: 1 1 100%;
+
+      .search-btn {
+        margin-left: 15px;
+      }
+    }
+
+    .el-form-item {
+      margin-bottom: 0;
+      margin-top: 15px;
+      flex: 0 0 calc(25% - 15px); /* 修改 */
+
+      &:nth-child(4n) {
+        margin-right: 0; /* 新增 */
+        flex: 0 0 calc(25%); /* 修改 */
+      }
+
+      &:not(:nth-child(4n)) {
+        margin-right: 15px; /* 新增 */
+      }
+    }
+  }
+  &-tabs {
+    margin: 20px 15px 0 15px;
+    .el-tabs__header {
+      margin: 0;
+    }
+  }
+  &-table {
+    flex: 1;
+    height: 100%;
+    padding: 15px;
+    overflow: auto;
+  }
+}
+.mb-0 {
+  margin-bottom: 0;
+}
+.mb-1 {
+  margin-bottom: 10px;
+}
+.mb-2 {
+  margin-bottom: 20px;
+}
+.mt-0 {
+  margin-top: 0;
+}
+.mt-1 {
+  margin-top: 10px;
+}
+.mt-2 {
+  margin-top: 20px;
+}
+.ml-0 {
+  margin-left: 0 !important;
+}
+.ml-1 {
+  margin-left: 10px !important;
+}
+.ml-2 {
+  margin-left: 20px !important;
+}
+.mr-0 {
+  margin-right: 0 !important;
+}
+.mr-1 {
+  margin-right: 10px !important;
+}
+.mr-2 {
+  margin-right: 20px !important;
+}
+
+.columnContainer {
+  display: flex;
+  align-items: start;
+  .title {
+    text-align: left;
+  }
+}
+
+.p-abs{
+	position: absolute;
+}
+.p-rel{
+	position: relative;
+}
+
+.abs_x {
+	position: absolute; left: 50%; transform: translateX(-50%);
+}
+
+.text-32 {
+	font-size:32px;
+}
+.text-333{
+	color: #333333;
+}
+.bottom-180px{
+	bottom:180px;
+}
+.bottom-260px{
+	bottom:260px;
+}
+
+.oprate-btns{
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+	.el-button{
+		margin-bottom: 10px;
+	}
+	.el-button+.el-button{
+		margin-left: 0px;
+	}
+}

+ 37 - 0
src/assets/style/transition.scss

@@ -0,0 +1,37 @@
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .2s;
+}
+
+.fade-transform-enter-from {
+  opacity: 0;
+  transform: translateX(-30px);
+  transition: all .2s;
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+  transition: all .2s;
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .2s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(80px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}

+ 6 - 0
src/assets/svg/read.md

@@ -0,0 +1,6 @@
+<!--
+ * @Date: 2023-05-24 14:25:36
+ * @Description: 
+ * @LastEditors: luoxi
+-->
+请在当前文件夹内放入svg文件,并删除当前文件

+ 31 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <svg class="SvgIcon" aria-hidden="true"><use :xlink:href="symbolId" /></svg>
+</template>
+
+<script lang="js">
+/** 使用参照文档:https://gitee.com/webxrd/vite-plugin-svg?_from=gitee_search */
+import { defineComponent, computed } from 'vue'
+export default defineComponent({
+  name: 'SvgIcon',
+  props: {
+    name: {
+      type: String,
+      required: true,
+    }
+  },
+  setup(props) {
+    const symbolId = computed(() => `#icon-${props.name}`)
+    return { symbolId }
+  }
+})
+</script>
+
+<style scoped>
+.SvgIcon {
+  font-size: inherit;
+  fill: currentColor;
+  width: 1em;
+  height: 1em;
+  text-indent: 0;
+}
+</style>

+ 42 - 0
src/components/charts/index.vue

@@ -0,0 +1,42 @@
+/**
+* 使用说明:用户只需传入options即可,options请参照官网示例中的options
+* 本组件采用整包引入echarts的方法,用于适配所有的echarts控件
+* 如需按需加载引入echarts,可参照写法:echarts官网/在打包环境中使用ECharts
+*/
+<template>
+  <div ref="chart" class="chart" />
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from 'vue'
+import * as echarts from 'echarts'
+import { useEventListener } from '@vueuse/core'
+const props = defineProps({
+  option: Object
+})
+const chart = ref(null)
+// 在onMounted事件才能拿到真实dom
+onMounted(() => {
+  const dom = chart.value
+  if (dom) {
+    let option = props.option
+    // 需要在页面Dom元素加载后再初始化echarts对象
+    let myChart = echarts.init(dom)
+    myChart.setOption(option)
+    // 自动监听加自动销毁
+    useEventListener('resize', () => myChart.resize())
+    watch(() => props.option, (newVal) => {
+      myChart.setOption(newVal)
+    },{ deep: true })
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .chart {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+  }
+</style>

+ 182 - 0
src/components/from/index.vue

@@ -0,0 +1,182 @@
+<template>
+  <el-form label-position="right" :class="['container', theme]" label-width="auto" class="layout-container-form"
+    :model="form" @submit.prevent>
+    <el-row :gutter="24">
+      <el-col v-for="item in formItems" :key="item.prop" :span="item.colSpan || 8" :xs="24" :sm="12" :md="12" :lg="8"
+        :xl="6">
+        <el-form-item :label="item.label" :label-width="item.labelWidth" class="form-item">
+          <el-input v-if="item.type === 'append'" v-model.number="form[item.prop]" :placeholder="item.placeholder"
+            clearable @clear="form[item.prop] = ''" class="fixed-width-input">
+            <template #append>岁</template>
+          </el-input>
+          <el-input v-if="item.type === 'input'" v-model.trim="form[item.prop]"
+            :placeholder="form[item.placeholder] || '请输入'" :clearable="true" @keyup.enter="handleEnter(item)"
+            @clear="form[item.prop] = ''" class="fixed-width-input"></el-input>
+          <el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :placeholder="item.placeholder || '请选择'"
+            filterable :clearable="item.clearable === false ? false : true" @clear="form[item.prop] = ''"
+            class="fixed-width-select">
+            <el-option v-for="option in item.options" :key="option.value" :label="option.label"
+              :value="option.value"></el-option>
+          </el-select>
+          <el-select v-else-if="item.type === 'selectGroup'" v-model="form[item.prop]" :clearable="true" filterable
+            class="fixed-width-select">
+            <el-option-group v-for="group in item.options" :key="group.id" :label="group.shelfName">
+              <el-option v-for="s in group.children" :key="s.id" :label="s.shelfName" :value="s.shelfName" />
+            </el-option-group>
+          </el-select>
+          <div v-else-if="item.type === 'date'">
+            <el-date-picker v-model="form[item.prop]" :type="item.dateType" :placeholder="item.placeholder"
+              :clearable="true" class="fixed-width-picker" style="width: 230px;"></el-date-picker>
+          </div>
+          <div v-else-if="item.type === 'daterange'">
+            <el-date-picker v-model="form[item.prop]" type="daterange" range-separator="至" start-placeholder="开始日期"
+              end-placeholder="结束日期" size="default" :clearable="true" style="width: 230px;"
+              :prefix-icon="CustomDateIcon" />
+          </div>
+          <div v-else-if="item.type === 'date'">
+            <el-date-picker v-model="form[item.prop]" type="date" :placeholder="item.placeholder || '请选择'" :size="default" />
+          </div>
+          <el-text v-if="item.type === 'text'">{{ item.label }}</el-text>
+          <div v-if="item.type === 'table'">
+            <slot></slot>
+          </div>
+        </el-form-item>
+      </el-col>
+      <el-col v-if="is_button" :span="6" class="is_button" :xs="24" :sm="12" :md="12" :lg="8" :xl="6">
+        <div v-if="formItems.length < 3" style="margin-left: 200px;"></div>
+        <div v-else style="width:15px"></div>
+        <el-button type="primary" :style="{ marginLeft: !is_reset ? '100px' : '' }" @click="submitForm">搜索</el-button>
+        <el-button class="default-but" @click="resetForm" v-if="is_reset">重置</el-button>
+        <el-button v-if="is_add_button" type="primary" @click="addForm">+ {{ is_add_button }}</el-button>
+      </el-col>
+    </el-row>
+
+  </el-form>
+</template>
+
+<script>
+import { reactive, defineProps, computed, ref } from "vue";
+import { useStore } from 'vuex'
+
+export default {
+  props: {
+    formItems: {
+      type: Array,
+      required: true,
+    },
+    is_button: {
+      type: Boolean,
+      default: true,
+    },
+    is_reset: {
+      type: Boolean,
+      default: true,
+    },
+    is_add_button: {
+      type: String,
+      default: '',
+    }
+  },
+  setup(props, { emit }) {
+    const store = useStore()
+    const form = reactive({
+      // 表单项的初始值
+    });
+    const roleId = computed(() => store.state.user.info.roleId);
+    const isTeam = ref(roleId.value == '1869679339119857665')  // 判断是否是团长
+    const theme = computed(() => isTeam.value ? 'team' : 'default');
+    const minWidth = computed(() => isTeam.value ? 6 : 8);
+    props.formItems.forEach(item => {
+      console.log('item', item)
+      if (item.defaultVal) {
+        Object.assign(form, {
+          [item.prop]: item.defaultVal
+        })
+      }
+    })
+    function submitForm() {
+      // 执行搜索逻辑
+      // 可以通过 form 获取表单数据进行搜索操作
+      // console.log("搜索表单提交", form);
+      // 触发自定义事件,并将数据作为参数传递给父组件
+      emit("formSubmitted", form);
+    }
+    function handleEnter(item) {
+      // 当用户按下回车键时,执行搜索逻辑
+      if (item.type === "input" && item.needEnterEvent) {
+        submitForm();
+      }
+    }
+
+    function resetForm() {
+      // 重置表单
+      for (const item of props.formItems) {
+        form[item.prop] = null;
+      }
+      emit("formReset");
+      // 重置其他表单项的值
+    }
+
+    function addForm() {
+      emit('addForm')
+    }
+
+    return {
+      form,
+      submitForm,
+      resetForm,
+      handleEnter,
+      addForm,
+      theme,
+      minWidth,
+    };
+  },
+};
+</script>
+
+<style scoped>
+.fixed-width-input,
+.fixed-width-select,
+.fixed-width-picker {
+  min-width: 250px;
+  max-width: 250px;
+}
+
+
+.is_button {
+  display: flex;
+  text-align: center;
+  margin-top: 15px;
+}
+
+
+::v-deep(.custom-date-picker .el-input__inner) {
+  width: 250px !important;
+  /* 你想要设置的宽度 */
+  border-bottom-color: red !important;
+}
+
+.container.team {
+  --system-text-color: #FFA74F;
+  --el-color-primary: #FFA74F;
+
+  ::v-deep(.el-form-item__label) {
+    font-weight: bold !important;
+    font-size: 16px;
+  }
+
+  ::v-deep(.el-date-editor .el-input__icon) {
+    font-size: 18px !important;
+  }
+
+  .el-button {
+    padding: 0px 20px;
+    font-size: 16px;
+  }
+
+  .default-but {
+    border: 1px solid var(--system-text-color);
+    color: var(--system-text-color);
+  }
+}
+</style>

+ 66 - 0
src/components/layer/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="layer" v-drag="layer.show">
+    <el-dialog v-model="layer.show" :title="layer.title" :width="layer.width" :top="layer.top" center
+      :before-close="close">
+      <el-scrollbar :height="layer.height">
+        <slot></slot>
+      </el-scrollbar>
+      <template #footer v-if="layer.showButton">
+        <div>
+          <el-button type="primary" @click="confirm">确认</el-button>
+          <el-button @click="close">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="js">
+  import { defineComponent } from 'vue'
+  import drag from '@/directive/drag/index'
+  export default defineComponent({
+    props: {
+      layer: {
+        type: Object,
+        default: () => {
+          return {
+            show: false,
+            title: '',
+            showButton: false,
+            width: 400,
+            top: '8vh',
+            height: '70vh'
+          }
+        },
+        required: true
+      }
+    },
+    directives: {
+      drag
+    },
+    setup(props, ctx) {
+      function confirm() {
+        ctx.emit('confirm')
+      }
+      function close() {
+        props.layer.show = false
+        ctx.emit('close')
+      }
+      return {
+        confirm,
+        close
+      }
+    }
+  })
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-dialog--center) {
+    min-width: 30vw;
+  }
+
+  :deep(.el-dialog__body) {
+    max-height: 60vh;
+    overflow: auto;
+  }
+</style>

+ 12 - 0
src/components/menu/index.vue

@@ -0,0 +1,12 @@
+<template>
+  <router-view></router-view>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+export default defineComponent({
+  setup() {
+
+  }
+})
+</script>

+ 98 - 0
src/components/table/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="system-table-box">
+    <el-table v-bind="$attrs" ref="table" class="system-table" border height="100%" :data="data"
+      @selection-change="handleSelectionChange">
+      <el-table-column type="selection" align="center" width="50" v-if="showSelection" />
+      <el-table-column label="序号" width="60" align="center" v-if="showIndex">
+        <template #default="scope">
+          {{ (page.index - 1) * page.size + scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <slot></slot>
+    </el-table>
+    <el-pagination v-if="showPage" v-model:current-page="page.index" class="system-page" background :layout="pageLayout"
+      :total="page.total" :page-size="page.size" :page-sizes="pageSizes" @current-change="handleCurrentChange"
+      @size-change="handleSizeChange">
+    </el-pagination>
+  </div>
+</template>
+
+<script lang="js">
+  import { defineComponent, ref, onActivated } from 'vue'
+  export default defineComponent({
+    props: {
+      data: { type: Array, default: () => [] }, // 数据源
+      select: { type: Array, default: () => [] }, // 已选择的数据,与selection结合使用
+      showIndex: { type: Boolean, default: false }, // 是否展示index选择,默认否
+      showSelection: { type: Boolean, default: false }, // 是否展示选择框,默认否
+      showPage: { type: Boolean, default: true }, // 是否展示页级组件,默认是
+      page: { // 分页参数
+        type: Object,
+        default: () => {
+          return { index: 1, size: 20, total: 0 }
+        }
+      },
+      pageLayout: { type: String, default: "total, sizes, prev, pager, next, jumper" }, // 分页需要显示的东西,默认全部
+      pageSizes: { type: Array, default: [10, 20, 50, 100] }
+    },
+    setup(props, context) {
+      const table = ref(null)
+      let timer = null
+      // 分页相关:监听页码切换事件
+      const handleCurrentChange = (val) => {
+        if (timer) {
+          props.page.index = 1
+        } else {
+          props.page.index = val
+          context.emit("getTableData")
+        }
+      }
+      // 分页相关:监听单页显示数量切换事件
+      const handleSizeChange = (val) => {
+        timer = 'work'
+        setTimeout(() => {
+          timer = null
+        }, 100)
+        props.page.size = val
+        context.emit("getTableData", true)
+      }
+      // 选择监听器
+      const handleSelectionChange = (val) => {
+        context.emit("selection-change", val)
+      }
+      // 解决BUG:keep-alive组件使用时,表格浮层高度不对的问题
+      onActivated(() => {
+        table.value.doLayout()
+      })
+      return {
+        table,
+        handleCurrentChange,
+        handleSizeChange,
+        handleSelectionChange
+      }
+    }
+  })
+</script>
+
+<style lang="scss" scoped>
+  .system-table-box {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: flex-start;
+    height: 100%;
+
+    .system-table {
+      flex: 1;
+      height: 100%;
+    }
+
+    .system-page {
+      margin-top: 20px;
+
+      &::before {
+        content: '';
+      }
+    }
+  }
+</style>

+ 18 - 0
src/config/index.js

@@ -0,0 +1,18 @@
+/*
+ * @Date: 2022-05-22 20:44:25
+ * @Description: 
+ */
+const showLogo = true; // 是否显示Logo顶部模块
+const systemTitle = '易推网络' // 系统名称,用于显示在左上角模块,登录模块、以及浏览器标题上使用,使用配置项
+const systemSubTitle = '渠道商营销管理系统' // 系统提示信息,用于登录模块使用
+
+/** 使用后端路由 */
+const isBackMenu = false
+
+
+export {
+  showLogo,
+  systemTitle,
+  systemSubTitle,
+  isBackMenu,
+}

+ 26 - 0
src/directive/clickOutside/index.js

@@ -0,0 +1,26 @@
+/**
+ * v-clickOutside
+ * 点击元素外部事件
+ * 接收参数:function类型,点击外面时返回true,点击内部时返回false
+ */
+const directive = {
+  mounted(el, binding) {
+    if (typeof binding.value !== 'function') {
+      throw 'callback must be a function'
+    }
+    el.__handleClick__ = function(e) {
+      if (el.contains(e.target)) {
+        binding.value(false)
+      } else {
+        binding.value(true)
+      }
+      
+    }
+    document.addEventListener('click', el.__handleClick__)
+  },
+  beforeUnmount(el) {
+    document.removeEventListener('click', el.__handleClick__)
+  }
+}
+
+export default directive

+ 33 - 0
src/directive/copy/index.js

@@ -0,0 +1,33 @@
+/**
+ * v-copy
+ * 复制某个值至剪贴板
+ * 接收参数:string类型/Ref<string>类型/Reactive<string>类型
+ */
+import { ElMessage } from 'element-plus'
+const directive = {
+  mounted(el, binding) {
+    el.copyData = binding.value
+    el.addEventListener('click', handleClick)
+  },
+  updated(el, binding) {
+    el.copyData = binding.value
+  },
+  beforeUnmount(el) {
+    el.removeEventListener('click', el.__handleClick__)
+  }
+}
+
+function handleClick(this, ev) {
+  let input = document.createElement('input')
+  input.value = this.copyData.toLocaleString()
+  document.body.appendChild(input)
+  input.select()
+  document.execCommand('Copy')
+  document.body.removeChild(input)
+  ElMessage({
+    type: 'success',
+    message: '复制成功'
+  })
+}
+
+export default directive

+ 28 - 0
src/directive/debounce/index.js

@@ -0,0 +1,28 @@
+/**
+ * v-debounce
+ * 按钮防抖指令,可自行扩展至input
+ * 接收参数:function类型
+ */
+const directive = {
+  mounted(el, binding) {
+    if (typeof binding.value !== 'function') {
+      console.error('callback must be a function')
+      return;
+    }
+    let timer = null
+    el.__handleClick__ = function(e) {
+      if (timer) {
+        clearInterval(timer)
+      }
+      timer = setTimeout(() => {
+        binding.value()
+      }, 200)
+    }
+    el.addEventListener('click', el.__handleClick__)
+  },
+  beforeUnmount(el) {
+    el.removeEventListener('click', el.__handleClick__)
+  }
+}
+
+export default directive

+ 141 - 0
src/directive/drag/index.js

@@ -0,0 +1,141 @@
+/**
+ * 指令,仅用于element-plus 中的 dialog 使用
+ * 可基于窗口各种拖拽使用
+ */
+const drag = {
+  mounted(el, binding) {
+    if (binding.value) {
+      handleElShow(el)
+    }
+  },
+  updated(el, binding) {
+    if (binding.value) {
+      handleElShow(el)
+    } else {
+      handleElHide(el)
+    }
+  },
+  beforeUnmount(el) {
+    handleElHide(el)
+  }
+}
+
+async function handleElShow(el) {
+  // 防止时间太快,拿不到dom
+  await myPromise()
+  const dialog = el.querySelector('.el-dialog')
+  if (!dialog) {
+    return
+  }
+  const header = el.querySelector('.el-dialog__header')
+  const dialogMask = el.querySelector('.el-overlay')
+  dialogMask.style.cssText += "overflow: hidden;"
+  header.style.cursor = 'move'
+  let dragStatus = false
+  let data = { // 数据源,不变部分为:window信息、dialog信息、mouse初始值信息,可变部分为:拖拽坐标位移
+    window: { // window信息
+      width: 0,
+      height: 0,
+    },
+    dialog: {
+      x: 0,
+      y: 0,
+      width: 0,
+      height: 0,
+      marginTop: ""
+    }, // dialog信息
+    mouse: { // 鼠标初始信息
+      x: 0,
+      y: 0
+    },
+    drag: { // 拖拽过程信息
+      x: 0,
+      y: 0
+    }
+  }
+  // 所有的监听只为了修改data数据
+  header.addEventListener('mousedown', mouseDown)
+  document.addEventListener('mousemove', mouseMove)
+  document.addEventListener('mouseup', mouseUp)
+  window.addEventListener('resize', sizeChange)
+  // 边界处理,防止拖动位置溢出
+  function handlePosition() {
+    if (data.mouse.x - data.drag.x >= data.dialog.x) {
+      data.drag.x = data.mouse.x - data.dialog.x
+    }
+    if (data.drag.x - data.mouse.x >= data.window.width - (data.dialog.x + data.dialog.width)) {
+      data.drag.x = data.mouse.x + data.window.width - data.dialog.x - data.dialog.width
+    }
+    if (data.mouse.y - data.drag.y >= data.dialog.y) {
+      data.drag.y = data.mouse.y - data.dialog.y
+    }
+    if (data.drag.y - data.mouse.y >= data.window.height - (data.dialog.y + data.dialog.height)) {
+      data.drag.y = data.mouse.y + data.window.height - data.dialog.y - data.dialog.height
+    }
+    setPosition()
+  }
+  // 根据data来设置拖动后的位置
+  function setPosition() {
+    let top = data.drag.y - data.mouse.y + data.dialog.y
+    let left = data.drag.x - data.mouse.x + data.dialog.x
+    dialog.style.cssText += `position: absolute; top: calc(${top}px - ${data.dialog.marginTop}); left: ${left}px;`
+  }
+  function mouseDown(e) {
+    // 获取dialog目前的位置,坐标, 以及屏幕当前的宽高
+    // 一切初始数据的获取应该放置于此,避免其他如:宽度修改等一系列的影响
+    if (e.button !== 0) {
+      return
+    }
+    data.window = {
+      width: document.body.clientWidth,
+      height: document.body.clientHeight
+    }
+    data.dialog = dialog.getBoundingClientRect()
+    data.dialog.marginTop = window.getComputedStyle(dialog).marginTop
+    data.mouse = {
+      x: e.clientX,
+      y: e.clientY
+    }
+    dragStatus = true
+  }
+  function mouseMove(e) {
+    if (dragStatus) {
+      data.drag = {
+        x: e.clientX,
+        y: e.clientY
+      }
+      dialogMask.style.userSelect = "none"
+      handlePosition()
+    }  
+  }
+  function mouseUp(e) {
+    dialogMask.style.userSelect = "auto"
+    dragStatus = false
+  }
+  function sizeChange(e) {
+    // dialog.style.cssText += 'position: static';
+  }
+  function myPromise() {
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        resolve()
+      }, 500)
+    })
+  }
+  // 方便卸载使用
+  el.__mouseDown__ = mouseDown
+  el.__mouseUp__ = mouseUp
+  el.__mouseMove__ = mouseMove
+  el.__sizeChange__ = sizeChange
+}
+
+function handleElHide(el) {
+  // 避免重复开销,卸载所有的监听
+  // 解决问题:多次创建新的实例 =》 监听不取消 =》 同时存在多个无用的监听,导致页面卡顿
+  document.removeEventListener('mousedown', el.__mouseDown__)
+  document.removeEventListener('mousemove', el.__mouseMove__)
+  document.removeEventListener('mouseup', el.__mouseUp__)
+  window.removeEventListener('resize', el.__sizeChange__)
+}
+
+export default drag

+ 151 - 0
src/directive/dragable/index.js

@@ -0,0 +1,151 @@
+/**
+ * 支持父级,自定义父级,以及window作为父级
+ * 使用示例:
+ * 1. v-dragable
+ * 2. v-dragable="'father'" // 使用父级作为父级
+ * 3. v-dragable="'body'" // 使用body对象作为父级
+ * 4. v-dragable="'#app'" // 使用id作为父级
+ * 5. v-dragable="'.list'" // 使用class名作为父级
+ * 3-5代表所有可被document.querySelector()解析的参数值
+ **/
+const directive = {
+  mounted: (el, binding) => {
+    setParentDom(el, binding, false)
+    // 子级元素位置处理
+    // 1. 获取父子元素当前位置
+    let parentDomRect
+    let elDomRect
+    let mouseData = {
+      down: { x: 0, y: 0},
+      move: { x: 0, y: 0 }
+    }
+    let mouseDown = false
+    el.__position__ = {
+      x: 0,
+      y: 0
+    }
+    let bodyUserSelect = 'text'
+    
+    function handleMouseDown(e) {
+      if (e.button !== 0) {
+        return
+      }
+      mouseData.down = {
+        x: e.clientX,
+        y: e.clientY
+      }
+      mouseDown = true
+      parentDomRect = el.__parentDom__.getBoundingClientRect()
+      elDomRect = el.getBoundingClientRect()
+      bodyUserSelect = document.querySelector('body').style.userSelect
+      document.querySelector('body').style.userSelect = "none"
+    }
+    function handleMouseMove(e) {
+      if (!mouseDown) {
+        return
+      }
+      mouseData.move = {
+        x: e.clientX,
+        y: e.clientY
+      }
+      setPosition()
+    }
+    function handleMouseUp(e) {
+      if (mouseDown) {
+        mouseDown = false
+        document.querySelector('body').style.userSelect = bodyUserSelect
+      }
+    }
+    // 用于设置el元素的Position位置
+    function setPosition() {
+      // 通过几何图形计算更佳,我就是通过几何画图计算出来的当前数据,使用者可以自行计算,得到这两个值
+      const x = mouseData.move.x + elDomRect.x - parentDomRect.x - mouseData.down.x
+      const y = mouseData.move.y + elDomRect.y - parentDomRect.y - mouseData.down.y
+      // 进行x,y坐标边界处理判断
+      if (x < 0) {
+        el.__position__.x = 0
+      } else if (x > parentDomRect.width - elDomRect.width) {
+        el.__position__.x = parentDomRect.width - elDomRect.width
+      } else {
+        el.__position__.x = x
+      }
+      if (y < 0) {
+        el.__position__.y = 0
+      } else if (y > parentDomRect.height - elDomRect.height) {
+        el.__position__.y = parentDomRect.height - elDomRect.height
+      } else {
+        el.__position__.y = y
+      }
+      // 渲染到真实dom属性上
+      el.style.cssText += `
+        position: absolute;
+        z-index: 100;
+        left: ${ el.__position__.x }px;
+        top: ${ el.__position__.y }px;
+      `
+    }
+    el.__mouseDown__ = handleMouseDown
+    el.__mouseMove__ = handleMouseMove
+    el.__mouseUp__ = handleMouseUp
+    // 2. 监听拖拽事件
+    el.addEventListener('mousedown', el.__mouseDown__)
+    document.addEventListener('mousemove', el.__mouseMove__)
+    document.addEventListener('mouseup', el.__mouseUp__)
+  },
+  updated(el, binding) {
+    setParentDom(el, binding, true)
+  },
+  beforeUnmount(el) {
+    // 避免重复开销,卸载所有的监听
+    // 解决问题:多次创建新的实例 =》 监听不取消 =》 同时存在多个无用的监听,导致页面性能变差
+    document.removeEventListener('mousedown', el.__mouseDown__)
+    document.removeEventListener('mousemove', el.__mouseMove__)
+    document.removeEventListener('mouseup', el.__mouseUp__)
+  }
+}
+// 设置parentDom,供mounted和update使用
+function setParentDom(el, binding, updated) {
+  
+  const array = [
+    { name: 'father', dom: el.parentElement }
+  ]
+  
+  // 获取父级元素
+  let parentDom
+  // 以下if操作用于确保一定有一个parentDom
+  if (binding.value) {
+    const findArr = array.find((arr) => {
+      return arr.name === binding.value
+    })
+    if (findArr && findArr.dom) {
+      parentDom = findArr.dom
+    } else {
+      parentDom = document.querySelector(binding.value) || array[0].dom || array[1].dom
+    }
+  } else {
+    parentDom = array[0].dom || array[1].dom
+  }
+  const parentDomRect = parentDom.getBoundingClientRect()
+  const elDomRect = el.getBoundingClientRect()
+  // 把以前的样式重置一下
+  if (el.__parentDom__) {
+    el.__parentDom__.style.position = 'static'
+  }
+  el.__parentDom__ = parentDom
+  el.__parentDom__.style.position = 'relative'
+  
+  if (updated) {
+    el.__position__ = {
+      x: elDomRect.x - parentDomRect.x,
+      y: elDomRect.y - parentDomRect.y
+    }
+    // return
+    el.style.cssText += `
+      position: absolute;
+      z-index: 100;
+      left: ${ el.__position__.x }px;
+      top: ${ el.__position__.y }px;
+    `
+  }
+}
+export default directive

+ 48 - 0
src/directive/longpress/index.js

@@ -0,0 +1,48 @@
+/**
+ * v-longpress
+ * 长按指令,长按时触发事件
+ */
+
+const directive = {
+  mounted(el, binding) {
+    if (typeof binding.value !== 'function') {
+      throw 'callback must be a function'
+    }
+    // 定义变量
+    let pressTimer = null
+    // 创建计时器( 2秒后执行函数 )
+    let start = (e) => {
+      if (e.button) {
+        if (e.type === 'click' && e.button !== 0) {
+          return
+        }
+      }
+      if (pressTimer === null) {
+        pressTimer = setTimeout(() => {
+          handler(e)
+        }, 1000)
+      }
+    }
+    // 取消计时器
+    let cancel = (e) => {
+      if (pressTimer !== null) {
+        clearTimeout(pressTimer)
+        pressTimer = null
+      }
+    }
+    // 运行函数
+    const handler = (e) => {
+      binding.value(e)
+    }
+    // 添加事件监听器
+    el.addEventListener('mousedown', start)
+    el.addEventListener('touchstart', start)
+    // 取消计时器
+    el.addEventListener('click', cancel)
+    el.addEventListener('mouseout', cancel)
+    el.addEventListener('touchend', cancel)
+    el.addEventListener('touchcancel', cancel)
+  },
+}
+
+export default directive

+ 30 - 0
src/directive/waterMarker/index.js

@@ -0,0 +1,30 @@
+/**
+ * v-waterMarker可接收参数,均为非必填
+ * { text: 'vue-admin-box', font: '16px Microsoft JhengHei', textColor: '#000' }
+ */
+
+const directive = {
+  mounted(el, binding) {
+    binding.value ? binding.value : binding.value = {}
+    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor, )
+  },
+}
+
+function addWaterMarker(str, parentNode, font, textColor) {
+  // 水印文字,父元素,字体,文字颜色
+  var can = document.createElement('canvas')
+  parentNode.appendChild(can)
+  can.width = 200
+  can.height = 150
+  can.style.display = 'none'
+  var cans = can.getContext('2d')
+  cans.rotate((-20 * Math.PI) / 180)
+  cans.font = font || '16px Microsoft JhengHei'
+  cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)'
+  cans.textAlign = 'left'
+  cans.textBaseline = 'middle'
+  cans.fillText(str ||'vue-admin-box' , can.width / 10, can.height / 2)
+  parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
+}
+
+export default directive

+ 219 - 0
src/hooks/useGetDictList.js

@@ -0,0 +1,219 @@
+/* import { ref } from 'vue'
+import { getCommonDictList } from '@/api/common'  // 假设这是你的 API 请求
+
+const DICT_CACHE_KEY = 'system_dict_data';
+
+export function useGetDictList() {
+    const dictData = ref({});
+
+    const loadDictData = async () => {
+        const cached = sessionStorage.getItem(DICT_CACHE_KEY);
+        if (cached) {
+            dictData.value = JSON.parse(cached);
+        } else {
+            const res = await getCommonDictList(); // 后端返回字典 JSON
+            dictData.value = res.data;
+            sessionStorage.setItem(DICT_CACHE_KEY, JSON.stringify(res.data));
+        }
+
+        const { uniqueTypes, optionsByType } = dictData.value.reduce((acc, item) => {
+            // 1. 收集唯一的 typeCode + typeName
+            const typeKey = `${item.typeCode}|${item.typeName}`;
+            if (!acc.uniqueTypeKeys.has(typeKey)) {
+                acc.uniqueTypes.push(
+                    {
+                        typeCode: item.typeCode,
+                        typeName: item.typeName
+                        //  dictionaryCode: item.dictionaryCode,
+                        //  dictionaryId: item.dictionaryId,
+                        //  dictionaryName: item.dictionaryName,
+                        //  enabled: item.enabled,
+                        //  sort: item.sort,
+                        //  typeCode: item.typeCode,
+                        //  typeName: item.typeName, 
+                    });
+                acc.uniqueTypeKeys.add(typeKey);
+            }
+
+            // 2. 按 typeCode 分组生成 options
+            if (!acc.optionsByType[item.typeCode]) {
+                acc.optionsByType[item.typeCode] = [];
+            }
+            acc.optionsByType[item.typeCode].push({
+                value: item.dictionaryCode,
+                label: item.dictionaryName
+            });
+
+            return acc;
+        },
+            { uniqueTypes: [], uniqueTypeKeys: new Set(), optionsByType: {} }
+        );
+
+        return uniqueTypes, optionsByType
+    };
+
+
+
+    // 拿到单个的Options
+    function getOptions(name) {
+        const options = dictData.value
+            .filter(item => item.typeCode === name) // 只取启用的项
+            .sort((a, b) => a.sort - b.sort)   // 按 sort 排序
+            .map(item => ({
+                label: item.dictionaryName,
+                value: Number(item.dictionaryCode) // 将字符串"1"/"0"转为数字
+            }));
+        return options
+    }
+
+    const clearDictCache = () => {
+        sessionStorage.removeItem(DICT_CACHE_KEY);
+    };
+
+    return {
+        dictData,//字典原数据
+        loadDictData,//加载字典
+        clearDictCache, //移除字典
+        getOptions,//字典 单个Options
+        // uniqueTypes, //单项字典合集
+        // optionsByType, //字典options合集
+    };
+}
+ */
+
+
+
+import { ref } from 'vue'
+import { getCommonDictList } from '@/api/common'
+
+const DICT_CACHE_KEY = 'system_dict_data'
+
+export function useGetDictList() {
+    const dictData = ref([])
+    const uniqueTypes = ref([])
+    const optionsByType = ref({})
+
+    /**
+     * 加载字典数据
+     * @param {boolean} forceRefresh - 是否强制刷新缓存
+     * @returns {Promise<{uniqueTypes: Array, optionsByType: Object}>}
+     */
+    const loadDictData = async (forceRefresh = false) => {
+        try {
+            // 强制刷新时清除缓存
+            if (forceRefresh) {
+                clearDictCache()
+            }
+
+            // 尝试从缓存读取
+            const cached = sessionStorage.getItem(DICT_CACHE_KEY)
+            if (cached && !forceRefresh) {
+                const parsedData = JSON.parse(cached)
+                dictData.value = parsedData || []
+            } else {
+                // 从API获取数据
+                const res = await getCommonDictList()
+                dictData.value = res.data || []
+                sessionStorage.setItem(DICT_CACHE_KEY, JSON.stringify(res.data))
+            }
+            
+
+            // 处理数据
+            const { uniqueTypes: ut, optionsByType: obt } = processDictData(dictData.value)
+            uniqueTypes.value = ut
+            optionsByType.value = obt
+
+            return { uniqueTypes: ut, optionsByType: obt }
+        } catch (error) {
+            console.error('加载字典数据失败:', error)
+            dictData.value = []
+            uniqueTypes.value = []
+            optionsByType.value = {}
+            return { uniqueTypes: [], optionsByType: {} }
+        }
+    }
+
+    /**
+     * 处理字典数据
+     * @param {Array} data - 字典数据
+     * @returns {{uniqueTypes: Array, optionsByType: Object}}
+     */
+    const processDictData = (data) => {
+        const result = data.reduce(
+            (acc, item) => {
+                // 收集唯一的 typeCode + typeName
+                const typeKey = `${item.typeCode}|${item.typeName}`
+                if (!acc.uniqueTypeKeys.has(typeKey)) {
+                    acc.uniqueTypes.push({
+                        typeCode: item.typeCode,
+                        typeName: item.typeName,
+                    })
+                    acc.uniqueTypeKeys.add(typeKey)
+                }
+
+                // 按 typeCode 分组生成 options
+                if (!acc.optionsByType[item.typeCode]) {
+                    acc.optionsByType[item.typeCode] = []
+                }
+                acc.optionsByType[item.typeCode].push({
+                    value: item.dictionaryCode,
+                    label: item.dictionaryName,
+                })
+
+                return acc
+            },
+            {
+                uniqueTypes: [],
+                uniqueTypeKeys: new Set(),
+                optionsByType: {},
+            }
+        )
+
+        return {
+            uniqueTypes: result.uniqueTypes,
+            optionsByType: result.optionsByType,
+        }
+    }
+
+    /**
+     * 获取指定类型的选项
+     * @param {string} typeCode - 字典类型代码
+     * @param {boolean} [filterEnabled=true] - 是否过滤未启用的项
+     * @returns {Array<{value: string|number, label: string}>}
+     */
+    const getOptions = (typeCode, filterEnabled = true) => {
+        if (!dictData.value.length) {
+            console.warn('字典数据未加载,请先调用 loadDictData')
+            return []
+        }
+
+        return dictData.value
+            .filter(item => item.typeCode === typeCode && (!filterEnabled || item.enabled === 1))
+            .sort((a, b) => a.sort - b.sort)
+            .map(item => ({
+                label: item.dictionaryName,
+                value: isNaN(Number(item.dictionaryCode))
+                    ? item.dictionaryCode
+                    : Number(item.dictionaryCode),
+            }))
+    }
+
+    /**
+     * 清除字典缓存
+     */
+    const clearDictCache = () => {
+        sessionStorage.removeItem(DICT_CACHE_KEY)
+        dictData.value = []
+        uniqueTypes.value = []
+        optionsByType.value = {}
+    }
+
+    return {
+        dictData, // 字典原数据
+        uniqueTypes, // 唯一的字典类型集合
+        optionsByType, // 按类型分组的选项
+        loadDictData, // 加载字典数据
+        clearDictCache, // 清除缓存
+        getOptions, // 获取指定类型的选项
+    }
+}

+ 65 - 0
src/layout/Header/Breadcrumb.vue

@@ -0,0 +1,65 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb hidden-sm-and-down" separator="/">
+    <transition-group appear name="breadcrumb">
+      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
+        <span
+          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
+          class="no-redirect"
+        >{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">
+          {{ item.meta.title }}
+        </a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script lang="js">
+import { ref, defineComponent, watch } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { isBackMenu } from '@/config'
+export default defineComponent({
+  name: "BreadCrumb",
+  setup() {
+    const levelList = ref([]);
+    const route = useRoute();
+    const router = useRouter();
+    const getBreadcrumb = () => {
+      let matched = route.matched.filter(item => item.meta && item.meta.title);
+      const first = matched[0];
+      levelList.value = matched.filter(
+        item => item.meta && item.meta.title && item.meta.breadcrumb !== false
+      );
+    };
+    getBreadcrumb();
+    watch(
+      () => route.path,
+      () => getBreadcrumb()
+    );
+    const handleLink = (item) => {
+      const { redirect, path } = item;
+      if (redirect) {
+        router.push(redirect.toString());
+        return;
+      }
+      router.push(path);
+    };
+    return { levelList, handleLink, isBackMenu };
+  }
+});
+</script>
+
+<style lang="scss" scoped >
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  .no-redirect {
+    color: var(--system-header-breadcrumb-text-color);
+    cursor: text;
+  }
+  a {
+    color: var(--system-header-text-color);
+  }
+}
+</style>

+ 31 - 0
src/layout/Header/functionList/fullscreen.vue

@@ -0,0 +1,31 @@
+<template>
+  <div :title="isFullscreen ? '退出全屏' : '全屏'">
+    <i class="sfont" :class="isFullscreen ? 'system-quanping':'system-quanping1'" @click="toggle"></i>
+  </div>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { useFullscreen } from '@vueuse/core'
+
+export default defineComponent({
+  name: 'fullscreen',
+  setup() {
+    const { isFullscreen, toggle } = useFullscreen()
+    return {
+      isFullscreen,
+      toggle
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  i {
+    cursor: pointer;
+    font-size: 18px;
+    &:focus {
+      outline: none;
+    }
+  }
+</style>

+ 25 - 0
src/layout/Header/functionList/github.vue

@@ -0,0 +1,25 @@
+<template>
+  <a href="https://github.com/cmdparkour/vue-admin-box" target="_blank" title="访问github地址"><i class="sfont system-github" @click="toggle"></i></a>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+export default defineComponent({
+
+})
+</script>
+
+<style lang="scss" scoped>
+  a {
+    &:focus {
+      outline: none;
+    }
+  }
+  i {
+    cursor: pointer;
+    font-size: 18px;
+    &:focus {
+      outline: none;
+    }
+  }
+</style>

+ 55 - 0
src/layout/Header/functionList/sizeChange.vue

@@ -0,0 +1,55 @@
+<template>
+  <el-dropdown @command="handleCommand" size="medium">
+    <span class="el-dropdown-link">
+      <i class="sfont system-zuixiaohua"></i>
+    </span>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-dropdown-item
+          v-for="d in list"
+          :key="d.size"
+          :command="d.size"
+          :disabled=" elementSize === d.size "
+        >
+          {{ d.name }}
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+
+<script lang="js">
+import { defineComponent, computed, unref } from 'vue'
+import { useRoute } from 'vue-router'
+import { useStore } from 'vuex'
+export default defineComponent({
+  setup() {
+    const store = useStore()
+    const route = useRoute()
+    const elementSize = computed(() => store.state.app.elementSize)
+    const list = [
+      { size: 'large', name: '大' },
+      { size: 'default', name: '默认' },
+      { size: 'small', name: '小' },
+    ]
+    const { fullPath } = unref(route)
+    return {
+      list,
+      elementSize,
+      fullPath
+    }
+  },
+  methods: {
+    handleCommand(command) {
+      this.$store.commit('app/stateChange', {
+        name: 'elementSize',
+        value: command
+      })
+    },
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  
+</style>

+ 177 - 0
src/layout/Header/functionList/theme.vue

@@ -0,0 +1,177 @@
+<template>
+  <div title="系统设置" @click="drawerChange(true)">
+    <i class="sfont system-shezhi"></i>
+  </div>
+  <el-drawer
+    title="系统设置"
+    v-model="drawer"
+    size="300px"
+    :show-close="false"
+    direction="rtl">
+      <h3>整体风格设置</h3>
+      <div class="theme-box">
+        <theme-icon
+          v-model:active="state.style"
+          v-for="(row, index) in style"
+          :key="index"
+          :name="index"
+          :tip="row.name"
+          :logo="row.logo.background"
+          :menu="row.menu.background"
+          :header="row.header.background"
+          :main="row.container.background"
+          :activeColor="row.page.color"
+        ></theme-icon>
+      </div>
+      <h3>主题色</h3>
+      <div class="theme-box">
+        <theme-color
+          v-for="(item, key) in themeColorArr"
+          v-model:active="state.primaryColor"
+          v-model:activeTextColor="state.primaryTextColor"
+          :key="key"
+          :color="item.color"
+          :textColor="item.textColor"
+          :tip="item.tip"
+        ></theme-color>
+      </div>
+      <h3>其他设置</h3>
+      <div class="list">
+        <div class="list-item" v-for="option in options" :key="option.name">
+          <span>{{ option.name }}</span>
+          <el-switch
+            v-model="option.value"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+            @change="change(option)"
+          >
+          </el-switch>
+        </div>
+      </div>
+  </el-drawer>
+</template>
+
+<script lang="js">
+import { defineComponent, ref, reactive, computed, watch } from 'vue'
+import { useStore } from 'vuex'
+import themeIcon from './theme/theme-icon.vue'
+import themeColor from './theme/theme-color.vue'
+import { style } from '@/theme/index'
+
+export default defineComponent({
+  components: {
+    themeIcon,
+    themeColor
+  },
+  setup() {
+    const store = useStore()
+    // 只取值,不做computed
+    const state = reactive({
+      style: store.state.app.theme.state.style,
+      primaryColor: store.state.app.theme.state.primaryColor,
+      primaryTextColor: store.state.app.theme.state.primaryTextColor,
+      menuType: store.state.app.theme.state.menuType
+    })
+    const themeColorArr = [
+      { color: '#409eff', textColor: '#fff', tip: '默认蓝' },
+      { color: '#d60f20', textColor: '#fff', tip: '玫瑰红' },
+      { color: '#ac25e6', textColor: '#fff', tip: '优雅紫' },
+      { color: '#4dc86f', textColor: '#fff', tip: '故事绿' },
+      { color: '#13c2c2', textColor: '#fff', tip: '明青' },
+      { color: '#333', textColor: '#fff', tip: '极客黑' }
+    ]
+    const setTheme = () => {
+      const userTheme = style[state.style]
+      const body = document.getElementsByTagName('body')[0]
+      // 设置全局顶部body上的class名称,即为主题名称,便于自定义配置符合当前主题的样式统一入口
+      // body.setAttribute('data-theme', state.style)
+      // 需要设置的颜色参照theme.scss,位置:assets/style/theme.scss
+      // 设置主题色
+      body.style.setProperty('--system-primary-color', state.primaryColor)
+      for (let i in userTheme) {
+        if (i === 'name') {
+          continue;
+        }
+        const item = userTheme[i]
+        for (let y in item) {
+          let cssVarName = '--system-' + i + '-' + y.replace(/([A-Z])/g, "-$1").toLowerCase()
+          body.style.setProperty(cssVarName, item[y])
+        }
+      }
+    }
+    // 监听数据的变化
+    watch(state, (newVal) => {
+      const theme = {
+        state: {
+          ...state
+        }
+      }
+      store.commit('app/stateChange', {
+        name: 'theme',
+        value: theme
+      })
+      setTheme()
+    })
+    let drawer = ref(false)
+    const options = reactive([
+      { name: '显示logo', value: store.state.app.showLogo, store: 'showLogo' },
+      { name: '显示面包屑导航', value: store.state.app.showTabs, store: 'showTabs' },
+      { name: '保持一个菜单展开', value: store.state.app.expandOneMenu, store: 'expandOneMenu' }
+    ])
+    const drawerChange = (value) => {
+      drawer.value = value
+    }
+    const change = (option) => {
+      store.commit(`app/stateChange`, { name: option.store, value: option.value })
+    }
+    setTheme()
+    return {
+      drawer,
+      options,
+      state,
+      style,
+      themeColorArr,
+      drawerChange,
+      change
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  i {
+    cursor: pointer;
+    &:focus {
+      outline: none;
+    }
+  }
+  
+  .list {
+    padding: 0 20px;
+    &-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 15px;
+      span {
+        font-size: 16px;
+      }
+    }
+  }
+  h3 {
+    margin-top: 40px;
+    margin-bottom: 20px;
+    color: rgba(0,0,0,.85);
+    font-size: 14px;
+    line-height: 22px;
+    text-align: left;
+    padding: 0 20px;
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+  .theme-box {
+    text-align: left;
+    padding-left: 20px;
+  }
+</style>

+ 77 - 0
src/layout/Header/functionList/theme/theme-color.vue

@@ -0,0 +1,77 @@
+<template>
+  <el-tooltip class="item" effect="dark" :content="tip" placement="top">
+    <div class="theme-color" :style="{ 'background-color': color }" @click="handleClick">
+      <div class="active" v-if="active === color">
+        <i class="sfont system-success" :style="{'color': textColor}"></i>
+      </div>
+    </div>
+  </el-tooltip>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+export default defineComponent({
+  props: {
+    active: {
+      type: String,
+      default: ''
+    },
+    activeTextColor: {
+      type: String,
+      default: ''
+    },
+    tip: {
+      type: String,
+      default: '默认蓝'
+    },
+    color: {
+      type: String,
+      default: '#409eff'
+    },
+    textColor: {
+      type: String,
+      default: '#fff'
+    }
+  },
+  setup(props, ctx) {
+    // 点击事件,触发v-model修改active值
+    const handleClick = () => {
+      ctx.emit('update:active', props.color)
+      ctx.emit('update:activeTextColor', props.textColor)
+    }
+    return {
+      handleClick
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .theme-color {
+    border-radius: 4px;
+    width: 20px;
+    height: 20px;
+    display: inline-block;
+    cursor: pointer;
+    outline: none;
+    position:relative;
+    .active {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0;
+      left: 0;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      i {
+        color: #fff;
+        font-weight: bold;
+        font-size: 12px;
+      }
+    }
+    &+.theme-color{
+      margin-left: 10px;
+    }
+  }
+</style>

+ 130 - 0
src/layout/Header/functionList/theme/theme-icon.vue

@@ -0,0 +1,130 @@
+<template>
+  <el-tooltip class="item" effect="dark" :content="tip" placement="top">
+    <div class="theme-icon" @click="handleClick">
+      <div class="theme-icon-sidebar">
+        <div class="theme-icon-sidebar-logo" :style="{ 'background-color': logo }"></div>
+        <div class="theme-icon-sidebar-menu" :style="{ 'background-color': menu }"></div>
+      </div>
+      <div class="theme-icon-content">
+        <div class="theme-icon-content-header" :style="{ 'background-color': header }"></div>
+        <div class="theme-icon-content-main" :style="{ 'background-color': main }">
+          <div class="active" v-if="active === name">
+            <i class="sfont system-success" :style="{'color': activeColor}"></i>
+          </div>
+        </div>
+      </div>
+      
+    </div>
+  </el-tooltip> 
+  
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+export default defineComponent({
+  props: {
+    name: {
+      type: String,
+      default: 'default'
+    },
+    active: {
+      type: String,
+      default: ''
+    },
+    menu: {
+      type: String,
+      defualt: ''
+    },
+    logo: {
+      type: String,
+      defualt: ''
+    },
+    header: {
+      type: String,
+      defualt: ''
+    },
+    main: {
+      type: String,
+      defualt: ''
+    },
+    tip: {
+      type: String,
+      default: '默认菜单风格'
+    },
+    activeColor: {
+      type: String,
+      default: ''
+    }
+  },
+  setup(props, ctx) {
+    // 点击事件,触发v-model修改active值
+    const handleClick = () => {
+      ctx.emit('update:active', props.name)
+    }
+    return {
+      handleClick
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .theme-icon {
+    display: inline-flex;
+    width: 50px;
+    height: 50px;
+    box-shadow: 0 1px 2.5px 0 rgba(0,0,0,.18);
+    cursor: pointer;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.12),0 0 6px rgba(0,0,0,0.04);
+    outline: none;
+    border-radius: 4px;
+    overflow: hidden;
+    &-sidebar{
+      width: 18px;
+      display: flex;
+      flex-direction: column;
+      &-logo{
+        width: 20px;
+        height: 10px;
+        background-color: #263445;
+      }
+      &-menu{
+        flex: 1;
+        background-color: rgb(40, 65, 90);
+      }
+    }
+    &-content{
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      &-header{
+        height: 10px;
+        background-color: #fff;
+      }
+      &-main{
+        flex: 1;
+        background-color: #f0f2f5;
+        position: relative;
+        .active {
+          position: absolute;
+          width: 100%;
+          height: 100%;
+          top: 0;
+          left: 0;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          i {
+            color: #000;
+            font-weight: bold;
+            font-size: 22px;
+          }
+        }
+      }
+    }
+
+  }
+  .theme-icon+.theme-icon{
+    margin-left: 12px;
+  }
+</style>

+ 168 - 0
src/layout/Header/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <header>
+    <div class="left-box">
+      <!-- 收缩按钮 -->
+      <div class="menu-icon" @click="opendStateChange">
+        <i class="sfont head-fold" :class="isCollapse ? 'system-s-unfold' : 'system-s-fold'"></i>
+      </div>
+      <Breadcrumb />
+    </div>
+    <div class="right-box">
+      <!-- 快捷功能按钮 -->
+      <div class="function-list">
+        <div class="function-list-item hidden-sm-and-down"><Full-screen /></div>
+        <div class="function-list-item">
+          <SizeChange />
+        </div>
+        <div class="function-list-item hidden-sm-and-down">
+          <Theme />
+        </div>
+        <!-- <div class="function-list-item hidden-sm-and-down"><Github /></div> -->
+      </div>
+      <!-- 用户信息 -->
+      <div class="user-info">
+        <el-dropdown>
+          <span class="el-dropdown-link" style="display: flex;align-items: center; min-width: 90px;justify-content: space-between;">
+            <el-avatar :size="40" :src="userInfo.headImage" />
+            <div>
+              {{userInfo.loginName}}
+              <i class="sfont system-xiala"></i>
+            </div>
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item @click="showPasswordLayer">修改密码</el-dropdown-item>
+              <el-dropdown-item @click="loginOut">退出登录</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+      <password-layer :layer="layer" v-if="layer.show" />
+    </div>
+  </header>
+</template>
+
+<script lang="js">
+  import { defineComponent, computed, reactive } from 'vue'
+  import { useStore } from 'vuex'
+  import { useRouter, useRoute } from 'vue-router'
+  import FullScreen from './functionList/fullscreen.vue'
+  import SizeChange from './functionList/sizeChange.vue'
+  import Github from './functionList/github.vue'
+  import Theme from './functionList/theme.vue'
+  import Breadcrumb from './Breadcrumb.vue'
+  import PasswordLayer from './passwordLayer.vue'
+  export default defineComponent({
+    components: {
+      FullScreen,
+      Breadcrumb,
+      SizeChange,
+      Github,
+      Theme,
+      PasswordLayer
+    },
+    setup() {
+      const store = useStore()
+      const router = useRouter()
+      const route = useRoute()
+      const layer = reactive({
+        show: false,
+        showButton: true
+      })
+      const isCollapse = computed(() => store.state.app.isCollapse)
+      const userInfo = computed(() => store.state.user.info)
+
+      // isCollapse change to hide/show the sidebar
+      const opendStateChange = () => {
+        store.commit('app/isCollapseChange', !isCollapse.value)
+      }
+
+      // login out the system
+      const loginOut = () => {
+        store.dispatch('user/loginOut')
+      }
+
+      const showPasswordLayer = () => {
+        layer.show = true
+      }
+      return {
+        isCollapse,
+        userInfo,
+        layer,
+        opendStateChange,
+        loginOut,
+        showPasswordLayer
+      }
+    }
+  })
+</script>
+
+<style lang="scss" scoped>
+  header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 60px;
+    background-color: var(--system-header-background);
+    padding-right: 22px;
+  }
+
+  .left-box {
+    height: 100%;
+    display: flex;
+    align-items: center;
+
+    .menu-icon {
+      width: 60px;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 25px;
+      font-weight: 100;
+      cursor: pointer;
+      margin-right: 10px;
+
+      &:hover {
+        background-color: var(--system-header-item-hover-color);
+      }
+
+      i {
+        color: var(--system-header-text-color);
+      }
+    }
+  }
+
+  .right-box {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .function-list {
+      display: flex;
+
+      .function-list-item {
+        width: 30px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        :deep(i) {
+          color: var(--system-header-text-color);
+        }
+      }
+    }
+
+    .user-info {
+      margin-left: 20px;
+
+      .el-dropdown-link {
+        color: var(--system-header-breadcrumb-text-color);
+      }
+    }
+  }
+
+  .head-fold {
+    font-size: 20px;
+  }
+</style>

+ 92 - 0
src/layout/Header/passwordLayer.vue

@@ -0,0 +1,92 @@
+<template>
+  <Layer :layer="layer" @confirm="submit" ref="layerDom">
+    <el-form :model="form" :rules="rules" ref="ruleForm" label-width="120px" style="margin-right:30px;">
+      <el-form-item label="用户名:" prop="name">
+        管理员
+      </el-form-item>
+      <el-form-item label="原密码:" prop="old">
+        <el-input v-model="form.old" placeholder="请输入原密码" show-password></el-input>
+      </el-form-item>
+			<el-form-item label="新密码:" prop="new">
+			  <el-input v-model="form.new" placeholder="请输入新密码" show-password></el-input>
+			</el-form-item>
+    </el-form>
+  </Layer>
+</template>
+
+<script lang="js">
+import { defineComponent, ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import { useStore } from 'vuex'
+import { passwordChange } from '@/api/user'
+import Layer from '@/components/layer/index.vue'
+export default defineComponent({
+  components: {
+    Layer
+  },
+  props: {
+    layer: {
+      type: Object,
+      default: () => {
+        return {
+          show: false,
+          title: '',
+          showButton: true
+        }
+      }
+    }
+  },
+  setup(props, ctx) {
+    const ruleForm = ref(null)
+    const layerDom = ref(null)
+    const store = useStore()
+    let form = ref({
+      userId: '123465',
+      name: '',
+      old: '',
+      new: ''
+    })
+    const rules = {
+      old: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
+      new: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
+    }
+    function submit() {
+      if (ruleForm.value) {
+        ruleForm.value.validate((valid) => {
+          if (valid) {
+            let params = {
+              id: form.value.userId,
+              old: form.value.old,
+              new: form.value.new
+            }
+            passwordChange(params)
+            .then(res => {
+              ElMessage({
+                type: 'success',
+                message: '密码修改成功,即将跳转到登录页面'
+              })
+              layerDom.value && layerDom.value.close()
+              setTimeout(() => {
+                store.dispatch('user/loginOut')
+              }, 2000)
+            })
+          } else {
+            return false;
+          }
+        });
+      }
+    }
+    return {
+      form,
+      rules,
+      layerDom,
+      ruleForm,
+      submit
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  
+</style>

+ 47 - 0
src/layout/Logo/index.vue

@@ -0,0 +1,47 @@
+<template>
+  <div class="logo-container">
+    <img v-if="showLogo" src="@/assets/logo.png" alt="">
+    <h1 v-if="!isCollapse">{{ systemSubTitle }}</h1>
+  </div>
+</template>
+
+<script lang="js">
+  import { defineComponent, computed } from 'vue'
+  import { useStore } from 'vuex'
+  import { showLogo, systemTitle, systemSubTitle } from '@/config'
+  export default defineComponent({
+    setup() {
+      const store = useStore()
+      const isCollapse = computed(() => store.state.app.isCollapse)
+      return {
+        showLogo,
+        isCollapse,
+        systemTitle,
+        systemSubTitle
+      }
+    }
+  })
+</script>
+
+<style lang="scss" scoped>
+  .logo-container {
+    height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    background-color: var(--system-logo-background);
+
+    img {
+      height: 30px;
+      width: 30px;
+    }
+
+    h1 {
+      margin-left: 10px;
+      font-size: 18px;
+      white-space: nowrap;
+      color: var(--system-logo-color);
+    }
+  }
+</style>

+ 41 - 0
src/layout/Menu/Link.vue

@@ -0,0 +1,41 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)" @click="hideMenu" >
+    <slot></slot>
+  </component>
+</template>
+
+<script lang="js">
+import { defineComponent, computed } from 'vue'
+import { useStore } from "vuex";
+export default defineComponent({
+  name: 'appLink',
+  props: {
+    to: {
+      type: String,
+      required: true
+    }
+  },
+  setup(props) {
+    const store = useStore();
+    const isCollapse = computed(() => store.state.app.isCollapse);
+    const linkProps = (to) => {
+     return {
+       to: to
+     } 
+    }
+    const hideMenu = () => {
+      if (document.body.clientWidth <= 1000 && !isCollapse.value) {
+        store.commit("app/isCollapseChange", true);
+      }
+    };
+    return {
+      type: "router-link",
+      linkProps,
+      hideMenu
+    }
+  }
+})
+</script>
+<style lang="">
+  
+</style>

+ 102 - 0
src/layout/Menu/MenuItem.vue

@@ -0,0 +1,102 @@
+<template>
+  <template v-if="!menu.hideMenu">
+    <el-sub-menu v-if="showMenuType === 2" :index="pathResolve" :show-timeout="0" :hide-timeout="0">
+      <template #title>
+        <i :class="menu.meta.icon" v-if="menu.meta.icon"></i>
+        <span>{{ menu.meta.title }}</span>
+      </template>
+      <menu-item v-for="(item, key) in menu.children" :key="key" :menu="item" :basePath="pathResolve" />
+    </el-sub-menu>
+    <app-link v-else-if="showMenuType === 1" :to="pathResolve">
+      <el-menu-item :index="pathResolve" v-if="!menu.children[0].children || menu.children[0].children.length === 0">
+        <i :class="menu.children[0].meta.icon || menu.meta.icon" v-if="menu.children[0].meta.icon || menu.meta.icon"></i>
+        <template #title>{{ menu.children[0].meta.title }}</template>
+      </el-menu-item>
+      <el-sub-menu v-else :index="pathResolve" :show-timeout="0" :hide-timeout="0">
+        <template #title>
+          <i :class="menu.children[0].meta.icon || menu.meta.icon" v-if="menu.children[0].meta.icon || menu.meta.icon"></i>
+          <span>{{ menu.children[0].meta.title }}</span>
+        </template>
+        <menu-item v-for="(item, key) in menu.children[0].children" :key="key" :menu="item" :basePath="pathResolve" />
+      </el-sub-menu>
+    </app-link>
+    <app-link v-else :to="pathResolve">
+      <el-menu-item :index="pathResolve">
+      <i :class="menu.meta.icon" v-if="menu.meta.icon"></i>
+      <template #title>{{ menu.meta.title }}</template>
+      </el-menu-item>
+    </app-link>
+  </template>
+</template>
+
+<script lang="js">
+import { defineComponent, computed } from 'vue'
+import appLink from './Link.vue'
+import { isBackMenu } from '@/config'
+export default defineComponent({
+  name: 'menu-item',
+  props: {
+    menu: {
+      type: Object,
+      required: true
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  components: {
+    appLink
+  },
+  setup(props) {
+    const menu = props.menu
+    // todo: 优化if结构
+    const showMenuType = computed(() => { // 0: 无子菜单, 1:有1个子菜单, 2:显示上下级子菜单
+      if (menu.children && (menu.children.length > 1 || (menu.children.length === 1 && menu.alwayShow))) {
+        return 2
+      } else if (menu.children && menu.children.length === 1 && !menu.alwayShow) {
+        return 1
+      } else {
+        return 0
+      }
+    })
+    // todo: 优化多层if
+    const pathResolve = computed(() => {
+      let path = ''
+      if (showMenuType.value === 1) {
+        if (menu.children[0].path.charAt(0) === '/') {
+          path = menu.children[0].path
+        } else {
+          let char = '/'
+          if (menu.path.charAt(menu.path.length - 1) === '/') {
+            char = ''
+          }
+          path = menu.path + char + menu.children[0].path
+        }
+      } else {
+        path = menu.path
+      }
+      path = props.basePath ? props.basePath + '/' + path : path
+      return path
+    })
+    return {
+      showMenuType,
+      pathResolve,
+      isBackMenu
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .el-sub-menu {
+    text-align: left;
+  }
+  .el-menu-item {
+    text-align: left;
+  }
+  .el-menu-item i, .el-sub-menu__title i {
+    padding-right: 8px;
+    font-size: 20px;
+  }
+</style>

+ 118 - 0
src/layout/Menu/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <el-scrollbar>
+    <el-menu
+      class="layout-menu system-scrollbar"
+      background-color="var(--system-menu-background)"
+      text-color="var(--system-menu-text-color)"
+      active-text-color="var(--system-primary-color)"
+      :default-active="activeMenu"
+      :class="isCollapse? 'collapse': ''"
+      :collapse="isCollapse"
+      :collapse-transition="false"
+      :unique-opened="expandOneMenu"
+    >
+      <menu-item v-for="(menu, key) in allRoutes" :key="key" :menu="menu" />
+    </el-menu>
+  </el-scrollbar>
+</template>
+
+<script lang="js">
+import { defineComponent, computed, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { useStore } from 'vuex'
+import MenuItem from './MenuItem.vue'
+export default defineComponent({
+  components: {
+    MenuItem
+  },
+  setup() {
+    const store = useStore()
+    const isCollapse = computed(() => store.state.app.isCollapse)
+    const expandOneMenu = computed(() => store.state.app.expandOneMenu)
+    const allRoutes = useRouter().options.routes
+    const route = useRoute()
+    const activeMenu = computed(() => {
+      const { meta, path } = route;
+      if (meta.activeMenu) {
+        return meta.activeMenu;
+      }
+      return path;
+    });
+    onMounted(() => {
+
+    })
+    return {
+      isCollapse,
+      expandOneMenu,
+      allRoutes,
+      activeMenu,
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .el-scrollbar {
+    background-color: var(--system-menu-background);
+  }
+  .layout-menu {
+    width: 100%;
+    border: none;
+    &.collapse {
+      margin-left: 0px;
+    }
+    :deep() {
+      .el-menu-item, .el-sub-menu {
+        background-color: var(--system-menu-background) !important;
+      }
+      .el-menu-item i, .el-menu-item-group__title, .el-sub-menu__title i {
+        color: var(--system-menu-text-color);
+      }
+      .el-menu-item, .el-sub-menu__title{
+        &.is-active {
+          background-color: var(--system-primary-color) !important;
+          color: var(--system-primary-text-color) !important;
+          i {
+            color: var(--system-primary-text-color) !important;
+          }
+          &:hover {
+            background-color: var(--system-primary-color) !important;
+            color: var(--system-primary-text-color) !important;
+          }
+        }
+        &:hover {
+          background-color: var(--system-menu-hover-background) !important;
+        }
+      }
+      .el-sub-menu {
+        &.is-active {
+          >.el-sub-menu__title, >.el-sub-menu__title i {
+            color: var(--system-menu-submenu-active-color) !important;
+          }
+        }
+        .el-menu-item {
+          background-color: var(--system-menu-children-background) !important;
+          &.is-active {
+            background-color: var(--system-primary-color) !important;
+            color: var(--system-primary-text-color) !important;
+            &:hover {
+              background-color: var(--system-primary-color) !important;
+              color: var(--system-primary-text-color) !important;
+            }
+          }
+          &:hover {
+            background-color: var(--system-menu-hover-background) !important;
+          }
+        }
+        .el-sub-menu {
+          .el-sub-menu__title {
+            background-color: var(--system-menu-children-background) !important;
+            &:hover {
+              background-color: var(--system-menu-hover-background) !important;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 308 - 0
src/layout/Tabs/index.vue

@@ -0,0 +1,308 @@
+<template>
+  <div class="tabs">
+    <el-scrollbar
+      class="scroll-container tags-view-container"
+      ref="scrollbarDom"
+      @wheel.passive="handleWhellScroll"
+      @scroll="handleScroll"
+    >
+      <Item
+        v-for="menu in menuList"
+        :key="menu.meta.title"
+        :menu="menu"
+        :active="activeMenu.path === menu.path"
+        @close="delMenu(menu)"
+        @reload="pageReload"
+      />
+    </el-scrollbar>
+    <div class="handle">
+      <div id="vueAdminBoxTabRefresh" @click="pageReload"></div>
+      <div id="vueAdminBoxTabCloseSelf" @click="closeCurrentRoute"></div>
+      <div id="vueAdminBoxTabCloseOther" @click="closeOtherRoute"></div>
+      <div id="vueAdminBoxTabCloseAll" @click="closeAllRoute"></div>
+      <el-dropdown placement="bottom">
+        <div class="el-dropdown-link">
+          <el-icon><ArrowDown /></el-icon>
+        </div>
+        <template #dropdown>
+          <el-dropdown-menu>
+            <el-dropdown-item class="tab-ddropdown-item" :icon="RefreshLeft" @click="pageReload">重新加载</el-dropdown-item>
+            <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="currentDisabled" @click="closeCurrentRoute">关闭当前标签</el-dropdown-item>
+            <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="menuList.length < 3" @click="closeOtherRoute">关闭其他标签</el-dropdown-item>
+            <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="menuList.length <= 1" @click="closeAllRoute">关闭所有标签</el-dropdown-item>
+          </el-dropdown-menu>
+        </template>
+      </el-dropdown>
+      <el-tooltip class="item" effect="dark" :content="contentFullScreen ? '退出全屏': '全屏'" placement="bottom">
+        <el-icon @click="onFullscreen"><FullScreen /></el-icon>
+      </el-tooltip>
+    </div>
+  </div>
+</template>
+
+<script lang="js">
+/** 引用vue系列函数 */
+import { defineComponent, computed, unref, watch, reactive, ref, nextTick } from 'vue'
+import { useStore } from 'vuex'
+import { useRoute, useRouter } from 'vue-router'
+
+/** 引用图标 */
+import { ArrowDown, RefreshLeft, CircleClose, FullScreen } from '@element-plus/icons'
+
+import Item from './item.vue'
+import tabsHook from './tabsHook'
+
+export default defineComponent({
+  components: {
+    Item, ArrowDown, FullScreen
+  },
+  setup() {
+    const store = useStore()
+    const route = useRoute()
+    const router = useRouter()
+    const scrollbarDom = ref(null)
+    const scrollLeft = ref(0)
+    const defaultMenu = {
+      path: '/dashboard',
+      meta: { title: '首页', hideClose: true }
+    }
+    const contentFullScreen = computed(() => store.state.app.contentFullScreen)
+    const currentDisabled = computed(() => route.path === defaultMenu.path)
+
+    let activeMenu = reactive({ path: '' })
+    let menuList = ref(tabsHook.getItem())
+    if (menuList.value.length === 0) { // 判断之前有没有调用过
+      addMenu(defaultMenu)
+    }
+    watch(menuList.value, (newVal) => {
+      tabsHook.setItem(newVal)
+    })
+    watch(menuList, (newVal) => {
+      tabsHook.setItem(newVal)
+    })
+    router.afterEach(() => {
+      addMenu(route)
+      initMenu(route)
+    })
+
+    // 全屏
+    function onFullscreen() {
+      store.commit('app/contentFullScreenChange', !contentFullScreen.value)
+    }
+    // 当前页面组件重新加载
+    function pageReload() {
+      const self = route.matched[route.matched.length-1].instances.default
+
+      self.handleReload();
+    }
+
+    // 关闭当前标签,首页不关闭
+    function closeCurrentRoute() {
+      if (route.path !== defaultMenu.path) {
+        const tab = document.getElementById('vueAdminBoxTabCloseSelf')
+        const nextPath = tab?.getAttribute('nextPath')
+        delMenu(route, nextPath)
+      }
+    }
+    // 关闭除了当前标签之外的所有标签
+    function closeOtherRoute() {
+      menuList.value = [defaultMenu]
+      if (route.path !== defaultMenu.path) {
+        addMenu(route)
+      }
+      setKeepAliveData()
+    }
+
+    // 关闭所有的标签,除了首页
+    function closeAllRoute() {
+      menuList.value = [defaultMenu]
+      setKeepAliveData()
+      router.push(defaultMenu.path)
+    }
+
+    // 添加新的菜单项
+    function addMenu(menu) {
+      let { path, meta, name, query } = menu
+      if (meta.hideTabs) {
+        return
+      }
+      let hasMenu = menuList.value.some((obj) => {
+        return obj.path === path
+      })
+      if (!hasMenu) {
+        menuList.value.push({
+          path,
+          meta,
+          name,
+          query
+        })
+      }
+    }
+
+    // 删除菜单项
+    function delMenu(menu, nextPath) {
+      let index = 0
+      if (!menu.meta.hideClose) {
+        if (menu.meta.cache && menu.name) {
+          store.commit('keepAlive/delKeepAliveComponentsName', menu.name)
+        }
+        index = menuList.value.findIndex((item) => item.path === menu.path)
+        menuList.value.splice(index, 1)
+      }
+      if (nextPath) {
+        router.push(nextPath)
+        return
+      }
+      // 若删除的是当前页面,回到前一页,若为最后一页,则回到默认的首页
+      if (menu.path === activeMenu.path) {
+        const prePage = index - 1 > 0 ? menuList.value[index - 1] : { path: defaultMenu.path }
+        router.push({ path: prePage.path, query: prePage.query || {} })
+      }
+    }
+
+    // 初始化activeMenu
+    function initMenu(menu) {
+      activeMenu = menu
+      nextTick(() => {
+        setPosition()
+      })
+    }
+    /** 设置当前滚动条应该在的位置 */
+    function setPosition() {
+      if (scrollbarDom.value) {
+        const domBox = {
+          scrollbar: scrollbarDom.value.wrapRef,
+          activeDom: scrollbarDom.value.wrapRef.querySelector('.active'),
+          activeFather: scrollbarDom.value.wrapRef.querySelector('.el-scrollbar__view')
+        }
+        let hasDoms = true
+        Object.keys(domBox).forEach((dom) => {
+          if (!dom) {
+            hasDoms = false
+          }
+        })
+        if (!hasDoms) {
+          return
+        }
+        const domData = {
+          scrollbar: domBox.scrollbar.getBoundingClientRect(),
+          activeDom: domBox.activeDom.getBoundingClientRect(),
+          activeFather: domBox.activeFather.getBoundingClientRect()
+        }
+        const num = domData.activeDom.x - domData.activeFather.x + 1/2 * domData.activeDom.width - 1/2 * domData.scrollbar.width
+        domBox.scrollbar.scrollLeft = num
+      }
+    }
+
+    // 配置需要缓存的数据
+    function setKeepAliveData() {
+      let keepAliveNames = []
+      menuList.value.forEach((menu) => {
+        menu.meta && menu.meta.cache && menu.name && keepAliveNames.push(menu.name)
+      })
+      store.commit('keepAlive/setKeepAliveComponentsName', keepAliveNames)
+    }
+
+    /** 监听鼠标滚动事件 */
+    function handleWhellScroll(e) {
+      let distance = 0
+      let speed = 5
+      if (e.wheelDelta > 30) {
+        distance = -10
+      } else if (e.wheelDelta < -30) {
+        distance = 10
+      }
+      // console.log(scrollLeft.value + eventDelta / 4)
+      scrollbarDom.value?.setScrollLeft(scrollLeft.value + distance * speed)
+    }
+
+    /** 监听滚动事件 */
+    function handleScroll({ scrollLeft: left }) {
+      scrollLeft.value = left
+    }
+
+    // 初始化时调用:1. 新增菜单 2. 初始化activeMenu
+    addMenu(route)
+    initMenu(route)
+    return {
+      RefreshLeft, CircleClose,
+      contentFullScreen,
+      scrollbarDom,
+      // 菜单相关
+      menuList,
+      activeMenu,
+      currentDisabled,
+      onFullscreen,
+      pageReload,
+      delMenu,
+      closeCurrentRoute,
+      closeOtherRoute,
+      closeAllRoute,
+      handleScroll,
+      handleWhellScroll
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .tabs {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 40px;
+    background: var(--system-header-background);
+    border-bottom: 1px solid var(--system-header-border-color);
+    border-top: 1px solid var(--system-header-border-color);
+    box-shadow: 0 1px 4px 0 rgba(0, 0, 0, .1);
+    .handle {
+      min-width: 95px;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      .el-dropdown-link {
+        margin-top: 5px;
+        border-left: 1px solid var(--system-header-border-color);
+        height: 25px;
+        width: 40px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      i {
+        color: var(--system-header-text-color);
+      }
+    }
+  }
+  .scroll-container {
+    white-space: nowrap;
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+    :deep(.el-scrollbar__bar) {
+      bottom: 0px;
+    }
+    :deep(.el-scrollbar__wrap) {
+      height: 49px;
+    }
+  }
+  .tags-view-container {
+    height: 34px;
+    flex: 1;
+    width: 100%;
+    display: flex;
+  }
+  .el-icon-full-screen {
+    cursor: pointer;
+    &:hover {
+      background: rgba(0,0,0,.025);
+    }
+    &:focus {
+      outline: none;
+    }
+  }
+  .tab-ddropdown-item {
+    display: flex;
+    align-items: center;
+  }
+</style>

+ 108 - 0
src/layout/Tabs/item.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="tags-view-item" :class="active? 'active' : ''">
+    <router-link :to="{ path: menu.path, query: menu.query }" v-if="menu.meta.title">
+      {{ menu.meta.title }}
+    </router-link>
+    <el-icon @click.stop="reload" v-if="active"><refresh-right /></el-icon>
+    <el-icon @click.stop="closeTab" v-if="!menu.meta.hideClose" alt="del"><close /></el-icon>
+  </div>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+import { RefreshRight, Close } from '@element-plus/icons'
+import { isBackMenu } from '@/config'
+export default defineComponent({
+  props: {
+    menu: {
+      type: Object,
+      default: () => {
+        return {
+          path: '',
+          meta: {
+            label: '',
+            hideClose: false
+          }
+        }
+      }
+    },
+    active: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    RefreshRight,
+    Close
+  },
+  setup(props, { emit }) {
+    // 关闭按钮
+    function closeTab() {
+      emit('close')
+    }
+    // 刷新按钮
+    function reload() {
+      emit('reload')
+    }
+    return {
+      closeTab,
+      reload,
+      isBackMenu
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .tags-view-item {
+    display: inline-flex;
+    align-items: center;
+    position: relative;
+    cursor: pointer;
+    height: 26px;
+    line-height: 26px;
+    border: 1px solid var(--system-header-border-color);
+    color: var(--system-header-text-color);
+    background: var(--system-header-tab-background);
+    padding: 0 8px;
+    font-size: 12px;
+    margin-left: 5px;
+    margin-top: 4px;
+    border-radius: 2px;
+    a {
+      color: var(--system-header-text-color);
+      height: 26px;
+      display: inline-block;
+      padding-left: 8px;
+      padding-right: 8px;
+    }
+    .el-icon-refresh-right {
+      display: inline-block;
+      margin-right: 5px;
+    }
+    .el-icon-close {
+      display: inline-block;
+      height: 26px;
+    }
+    &:first-of-type {
+      margin-left: 15px;
+    }
+    &:last-of-type {
+      margin-right: 15px;
+    }
+    &.active {
+      background: var(--system-primary-color);
+      border-color: var(--system-primary-color);
+      color: var(--system-primary-text-color);
+      a {
+        color: var(--system-primary-text-color);
+      }
+      &:hover {
+        background: var(--system-primary-color);
+      }
+    }
+    &:hover {
+      background-color: var(--system-header-item-hover-color);
+    }
+  }
+</style>

+ 11 - 0
src/layout/Tabs/tabsHook.js

@@ -0,0 +1,11 @@
+
+/** 设置和拉取tab数据的主方法 */
+const tabsHook = {
+  setItem: function(arr) {
+    localStorage.setItem('tabs', JSON.stringify(arr))
+  },
+  getItem: function() {
+    return JSON.parse(localStorage.getItem('tabs') || '[]')
+  }
+}
+export default tabsHook

+ 145 - 0
src/layout/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <el-container style="height: 100vh">
+    <div
+      class="mask"
+      v-show="!isCollapse && !contentFullScreen"
+      @click="hideMenu"
+    ></div>
+    <el-aside
+      :width="isCollapse ? '60px' : '250px'"
+      :class="isCollapse ? 'hide-aside' : 'show-side'"
+      v-show="!contentFullScreen"
+    >
+      <Logo v-if="showLogo" />
+      <Menu />
+    </el-aside>
+    <el-container>
+      <el-header v-show="!contentFullScreen">
+        <Header />
+      </el-header>
+      <Tabs v-show="showTabs" />
+      <el-main>
+        <router-view v-slot="{ Component, route }">
+          <transition
+            :name="route.meta.transition || 'fade-transform'"
+            mode="out-in"
+          >
+            <keep-alive
+              v-if="keepAliveComponentsName"
+              :include="keepAliveComponentsName"
+            >
+              <component :is="Component" :key="route.fullPath" />
+            </keep-alive>
+            <component v-else :is="Component" :key="route.fullPath" />
+          </transition>
+        </router-view>
+      </el-main>
+    </el-container>
+  </el-container>
+</template>
+
+<script lang="js">
+import { defineComponent, computed, onBeforeMount } from "vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import { useEventListener } from "@vueuse/core";
+import Menu from "./Menu/index.vue";
+import Logo from "./Logo/index.vue";
+import Header from "./Header/index.vue";
+import Tabs from "./Tabs/index.vue";
+export default defineComponent({
+  components: {
+    Menu,
+    Logo,
+    Header,
+    Tabs,
+  },
+  setup() {
+    const store = useStore();
+    // computed
+    const isCollapse = computed(() => store.state.app.isCollapse);
+    const contentFullScreen = computed(() => store.state.app.contentFullScreen);
+    const showLogo = computed(() => store.state.app.showLogo);
+    const showTabs = computed(() => store.state.app.showTabs);
+    const keepAliveComponentsName = computed(() => store.getters['keepAlive/keepAliveComponentsName']);
+    // 页面宽度变化监听后执行的方法
+    const resizeHandler = () => {
+      if (document.body.clientWidth <= 1000 && !isCollapse.value) {
+        store.commit("app/isCollapseChange", true);
+      } else if (document.body.clientWidth > 1000 && isCollapse.value) {
+        store.commit("app/isCollapseChange", false);
+      }
+    };
+    // 初始化调用
+    resizeHandler();
+    // beforeMount
+    onBeforeMount(() => {
+      // 监听页面变化
+      useEventListener("resize", resizeHandler);
+    });
+    // methods
+    // 隐藏菜单
+    const hideMenu = () => {
+      store.commit("app/isCollapseChange", true);
+    };
+    return {
+      isCollapse,
+      showLogo,
+      showTabs,
+      contentFullScreen,
+      keepAliveComponentsName,
+      hideMenu,
+    };
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.el-header {
+  padding-left: 0;
+  padding-right: 0;
+}
+.el-aside {
+  display: flex;
+  flex-direction: column;
+  transition: 0.2s;
+  overflow-x: hidden;
+  transition: 0.3s;
+  &::-webkit-scrollbar {
+    width: 0 !important;
+  }
+}
+.el-main {
+  background-color: var(--system-container-background);
+  height: 100%;
+  padding: 0;
+  overflow-x: hidden;
+}
+:deep(.el-main-box) {
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  box-sizing: border-box;
+}
+@media screen and (max-width: 1000px) {
+  .el-aside {
+    position: fixed;
+    top: 0;
+    left: 0;
+    height: 100vh;
+    z-index: 1000;
+    &.hide-aside {
+      left: -250px;
+    }
+  }
+  .mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100vw;
+    height: 100vh;
+    z-index: 999;
+    background: rgba(0, 0, 0, 0.5);
+  }
+}
+</style>

+ 31 - 0
src/main.js

@@ -0,0 +1,31 @@
+/*
+ * @Date: 2022-05-22 20:44:25
+ * @Description: 
+ */
+import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import locale from "element-plus/es/locale/lang/zh-cn";
+import { baidu } from './utils/system/statistics'
+import 'element-plus/theme-chalk/display.css' // 引入基于断点的隐藏类
+import 'element-plus/dist/index.css'
+import 'normalize.css' // css初始化
+import './assets/style/common.scss' // 公共css
+import './theme/modules/chinese/index.scss'
+import App from './App.vue'
+import store from './store'
+import router from './router'
+import { getAuthRoutes } from './router/permission'
+if (import.meta.env.MODE !== 'development') { // 非开发环境调用百度统计
+  baidu()
+}
+
+/** 权限路由处理主方法 */
+getAuthRoutes().then(() => {
+  const app = createApp(App)
+  app.use(ElementPlus, { size: store.state.app.elementSize, locale: locale })
+  app.use(store)
+  app.use(router)
+  // app.config.performance = true
+  app.mount('#app')
+})
+

+ 46 - 0
src/router/createNode.js

@@ -0,0 +1,46 @@
+// 1. 用于解决keep-alive需要name的问题,动态生成随机name供keep-alive使用
+// 2. 用于解决transition动画内部结点只能为根元素的问题,单文件可写多结点
+import { defineComponent, h, createVNode, ref, nextTick } from 'vue'
+import reload from './reload.vue'
+import NProgress from '@/utils/system/nprogress'
+
+export function createNameComponent(component) {
+  return () => {
+    return new Promise((resolve) => {
+      component().then((comm) => {
+        const name = (comm.default.name || 'vueAdminBox') + '$' + Date.now();
+        const tempComm = defineComponent({
+          name,
+          setup() {
+            const isReload = ref(false);
+            let timeOut = null;
+            const handleReload = () => {
+              isReload.value = true;
+              timeOut && clearTimeout(timeOut);
+              NProgress.start();
+              timeOut = setTimeout(() => {
+                nextTick(() => {
+                  NProgress.done();
+                  isReload.value = false;
+                });
+              }, 260);
+            };
+            return {
+              isReload,
+              handleReload
+            };
+          },
+          render: function () {
+            if (this.isReload) {
+              return h('div', { class: 'el-main-box' }, [h(reload)]);
+            } else {
+              return h('div', { class: 'el-main-box' }, [createVNode(comm.default)]);
+            }
+          }
+        });
+        resolve(tempComm);
+      });
+    });
+  }
+}
+

+ 75 - 0
src/router/index.js

@@ -0,0 +1,75 @@
+/*
+ * @Author: luoxi
+ * @Date: 2022-01-25 09:51:12
+ * @LastEditors: luoxi
+ * @LastEditTime: 2022-01-25 12:25:51
+ * @FilePath: \vue-admin-box\src\router\index.ts
+ * @Description: 
+ */
+/**
+ * @description 所有人可使用的参数配置列表
+ * @params hideMenu: 是否隐藏当前路由结点不在导航中展示
+ * @params alwayShow: 只有一个子路由时是否总是展示菜单,默认false
+ */
+import { reactive } from 'vue'
+import { createRouter, createWebHashHistory } from 'vue-router'
+import store from '@/store'
+import NProgress from '@/utils/system/nprogress'
+import { changeTitle } from '@/utils/system/title'
+
+NProgress.configure({ showSpinner: false })
+
+// 引入不需要权限的modules
+import System from './modules/system'
+
+/** 
+ * @name 初始化必须要的路由
+ * @description 使用reactive属性使得modules可以在路由菜单里面实时响应,搞定菜单回显的问题
+ * @detail 针对modules的任何修改,均会同步至菜单级别,记住,是针对变量名为:moduels的修改
+ **/
+let modules = reactive([
+  ...System
+])
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes: modules
+})
+
+// 未授权时可访问的白名单
+const whiteList = ['/login']
+
+// 路由跳转前的监听操作
+router.beforeEach((to, _from, next) => {
+  NProgress.start();
+  if (store.state.user.token) {
+    to.meta.title ? (changeTitle(to.meta.title)) : "" // 动态title
+    if (to.path === '/login') {
+      next('/')
+      return
+    }
+    next()
+  } else if (whiteList.includes(to.path)) {
+    to.meta.title ? (changeTitle(to.meta.title)) : "" // 动态title
+    next()
+  } else {
+    next("/login"); // 全部重定向到登录页
+    to.meta.title ? (changeTitle(to.meta.title)) : "" // 动态title
+  }
+});
+
+// 路由跳转后的监听操作
+router.afterEach((to, _from) => {
+  const keepAliveComponentsName = store.getters['keepAlive/keepAliveComponentsName'] || []
+  const name = to.matched[to.matched.length - 1].components.default.name
+  if (to.meta && to.meta.cache && name && !keepAliveComponentsName.includes(name)) {
+    store.commit('keepAlive/addKeepAliveComponentsName', name)
+  }
+  NProgress.done()
+});
+
+export {
+  modules
+}
+
+export default router

+ 21 - 0
src/router/modules/agentModule.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/agentModule',
+        redirect: '/agentModule/index',
+        component: Layout,
+        meta: { title: '代理模块', icon: 'sfont system-component' },
+        children: [
+            {
+                path: '/agentModule/index',
+                name: 'AgentModuleIndex',
+                component: createNameComponent(() => import('@/views/main/agentModule/index.vue')),
+                meta: { title: '代理模块' },
+            },
+        ],
+    }
+]
+
+export default route

+ 19 - 0
src/router/modules/dashboard.js

@@ -0,0 +1,19 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+const route = [
+  {
+    path: '/',
+    component: Layout,
+    redirect: '/dashboard',
+    meta: { title: 'dashboard', icon: 'iconfont icon-home' },
+    children: [
+      {
+        path: 'dashboard',
+        component: createNameComponent(() => import('@/views/main/dashboard/index.vue')),
+        meta: { title: '首页', icon: 'iconfont icon-home', hideClose: true }
+      }
+    ]
+  }
+]
+
+export default route

+ 20 - 0
src/router/modules/formworkErection.js

@@ -0,0 +1,20 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/formworkErection',
+        component: Layout,
+        redirect: '/formworkErection/index',
+        meta: { title: '搭建模板', icon: 'sfont system-zidingyi' },
+        children: [
+            {
+                path: '/formworkErection/index',
+                component: createNameComponent(() => import('@/views/main/formworkErection/index.vue')),
+                meta: { title: '搭建模板' },
+            },
+        ],
+    }
+]
+
+export default route

+ 21 - 0
src/router/modules/operatorModule.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/operatorModule',
+        redirect: '/operatorModule/index',
+        component: Layout,
+        meta: { title: '运营模块', icon: 'sfont system-shequ' },
+        children: [
+            {
+                path: '/operatorModule/index',
+                name: 'OperatorModule',
+                component: createNameComponent(() => import('@/views/main/operatorModule/index.vue')),
+                meta: { title: '运营模块' },
+            },
+        ],
+    },
+]
+
+export default route

+ 21 - 0
src/router/modules/riskModule.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/riskModule',
+        redirect: '/riskModule/firstLevelRiskControl/index',
+        component: Layout,
+        meta: { title: '风控模块', icon: 'iconfont icon-jianguanfengkong' },
+        children: [
+            {
+                path: 'riskControlConfig/index',
+                name: 'RiskControlConfig',
+                component: createNameComponent(() => import('@/views/main/riskModule/riskControlConfig.vue')),
+                meta: { title: '风控模块' },
+            },
+        ],
+    }
+]
+
+export default route

+ 44 - 0
src/router/modules/system.js

@@ -0,0 +1,44 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+const route = [
+  {
+    path: '/system',
+    component: Layout,
+    redirect: '/404',
+    hideMenu: true,
+    meta: { title: '系统目录' },
+    children: [
+      {
+        path: '/404',
+        component: createNameComponent(() => import('@/views/system/404.vue')),
+        meta: { title: '404', hideTabs: true }
+      },
+      {
+        path: '/401',
+        component: createNameComponent(() => import('@/views/system/401.vue')),
+        meta: { title: '401', hideTabs: true }
+      },
+      {
+        path: '/redirect/:path(.*)',
+        component: createNameComponent(() => import('@/views/system/redirect.vue')),
+        meta: { title: '重定向页面', hideTabs: true }
+      }
+    ]
+  },
+  {
+    path: '/login',
+    component: createNameComponent(() => import('@/views/system/login.vue')),
+    hideMenu: true,
+    meta: { title: '登录', hideTabs: true }
+  },
+  {
+    // 找不到路由重定向到404页面
+    path: "/:pathMatch(.*)",
+    component: Layout,
+    redirect: "/404",
+    hideMenu: true,
+    meta: { title: '' },
+  },
+]
+
+export default route

+ 39 - 0
src/router/modules/userModule.js

@@ -0,0 +1,39 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/userModule',
+        redirect: '/userModule/onlineList/index',
+        component: Layout,
+        meta: { title: '用户模块', icon: 'iconfont icon-user' },
+        children: [
+            {
+                path: 'onlineList/index',
+                name: 'OnlineList',
+                component: createNameComponent(() => import('@/views/main/userModule/onlineList.vue')),
+                meta: { title: '在线列表' },
+            },
+            {
+                path: 'userList/index',
+                name: 'UserList',
+                component: createNameComponent(() => import('@/views/main/userModule/userList.vue')),
+                meta: { title: '用户列表' },
+            },
+            {
+                path: 'relieveLogsList/index',
+                name: 'RelieveLogsList',
+                component: createNameComponent(() => import('@/views/main/userModule/relieveLogsList.vue')),
+                meta: { title: '解封日志表' },
+            },
+            {
+                path: 'riskLogsList/index',
+                name: 'RiskLogsList',
+                component: createNameComponent(() => import('@/views/main/userModule/riskLogsList.vue')),
+                meta: { title: '风控日志表' },
+            },
+        ],
+    },
+]
+
+export default route

+ 21 - 0
src/router/modules/withdrawModule.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/withdrawModule',
+        redirect: '/withdrawModule/index',
+        component: Layout,
+        meta: { title: '提现模块', icon: 'sfont system-xitongzhuangtai' },
+        children: [
+            {
+                path: '/withdrawModule/index',
+                name: 'WithdrawModuleIndex',
+                component: createNameComponent(() => import('@/views/main/withdrawModule/index.vue')),
+                meta: { title: '提现模块' },
+            },
+        ],
+    }
+]
+
+export default route

+ 55 - 0
src/router/permission.js

@@ -0,0 +1,55 @@
+/*
+ * @Date: 2022-07-24 21:43:47
+ * @Description: 
+ */
+/** 引入类型 */
+
+/** 引入路由相关的资源 */
+import router, { modules } from './index'
+/** 引入vuex实例 */
+import store from '@/store'
+/** 引入后端路由控制器 */
+import { isBackMenu } from '@/config'
+
+/** 引入纯前端路由 */
+import FrontRoutes from './permission/front'
+/** 引入后端路由 */
+import getMenu from './permission/back'
+
+/** 
+ * @name 动态路由的权限新增,供登录后调用
+ * @other 如果需要进行后端接口控制菜单的话,请在此拿到后端的菜单树与asyncRoutes对比,生成一个新的值
+ */
+async function addRoutes() {
+  // 已验证完成,下面代码添加的可以实时同步至菜单中去,可以添加setTimeout(() => {}) 模拟异步代码的操作
+  // 利用前端路由表模拟后端数据问题
+  // 等待后端接口返回数据后再回调出去,防止刷新跳转404
+  return new Promise((resolve) => {
+    if (isBackMenu) {
+      getMenu()
+      .then((data) => {
+        data.forEach(item => {
+          modules.push(item)
+          router.addRoute(item)
+        })
+        resolve()
+      })
+      return
+    }    
+    FrontRoutes.forEach(item => {
+      modules.push(item)
+      router.addRoute(item)
+    })
+    resolve()
+  })
+}
+
+/**
+ * @des 登录了之后会执行这个方法,实现动态路由的功能
+ */
+export async function getAuthRoutes() {
+  // 判断token是否存在,存在则调用添加路由的方法
+  if (store.state.user.token) {
+    await addRoutes()
+  }
+}

+ 48 - 0
src/router/permission/back.js

@@ -0,0 +1,48 @@
+/** 动态路由实现基础组件 */
+/** 引入全局Layout组件 */
+import Layout from '@/layout/index.vue'
+/** 引入多级菜单控制器组件 */
+import MenuBox from '@/components/menu/index.vue'
+/** 引入带有系统自定义name的组件,方便keep-alive实现 */
+import { createNameComponent } from '../createNode'
+/** 引入所有的配置清单 */
+import backConfig from './backConfig'
+import { getMenuApi } from '@/api/user'
+
+/** 获取后台模板配置清单 */
+const getMenu = async () => {
+  const result = await getMenuApi()
+  const backRoutes = getComponents(result.data.list)
+  return backRoutes
+}
+
+/** 循环取出component */
+const getComponents = (data, level = 1) => {
+  const newData = data.map((item) => {
+    if (item.children) {
+      if (level == 1) {
+        return {
+          ...item,
+          component: Layout,
+          children: getComponents(item.children, level + 1)
+        }
+      } else {
+        return {
+          ...item,
+          component: MenuBox,
+          children: getComponents(item.children, level + 1)
+        }
+      }
+    } else {
+      const [first, end] =  item.component.split('_')
+      const component = backConfig[first][end]
+      return {
+        ...item,
+        component,
+      }
+    }
+  })
+  return newData
+}
+
+export default getMenu

+ 51 - 0
src/router/permission/backConfig.js

@@ -0,0 +1,51 @@
+/*
+ * @Date: 2023-03-10 19:34:30
+ * @Description: 
+ */
+import { createNameComponent } from '../createNode';
+
+/** 首页系列 */
+const dashboard = {
+  /** 首页 */
+  dashboard: createNameComponent(() => import('@/views/main/dashboard/index.vue')),
+}
+
+const formworkErection = {
+  formworkErection: createNameComponent(() => import('@/views/main/formworkErection/index.vue')),
+}
+
+const userModule = {
+  onlineList: createNameComponent(() => import('@/views/main/userModule/onlineList.vue')),
+  userList: createNameComponent(() => import('@/views/main/userModule/userList.vue')),
+  relieveLogsList: createNameComponent(() => import('@/views/main/userModule/relieveLogsList.vue')),
+  riskLogsList: createNameComponent(() => import('@/views/main/userModule/riskLogsList.vue')),
+}
+
+const withdrawModule = {
+  withdrawModule: createNameComponent(() => import('@/views/main/formworkErection/index.vue')),
+}
+
+const agentModule = {
+  agentModule: createNameComponent(() => import('@/views/main/agentModule/index.vue')),
+}
+
+const operatorModule = {
+  operatorModule: createNameComponent(() => import('@/views/main/operatorModule/index.vue')),
+}
+
+const riskModule = {
+  riskControlConfig: createNameComponent(() => import('@/views/main/riskModule/riskControlConfig.vue')),
+}
+
+/** 导出所有路由,供后端配置使用 */
+const allRoutes = {
+  dashboard,
+  formworkErection,
+  userModule,
+  withdrawModule,
+  agentModule,
+  operatorModule,
+  riskModule,
+}
+
+export default allRoutes

+ 27 - 0
src/router/permission/front.js

@@ -0,0 +1,27 @@
+/**
+ * 前端路由管理
+ **/
+
+/** 引入需要权限的Modules */
+import Dashboard from '../modules/dashboard'
+import FormworkErection from '../modules/formworkErection'
+import UserModule from '../modules/userModule'
+import WithdrawModule from '../modules/withdrawModule'
+import AgentModule from '../modules/agentModule'
+import OperatorModule from '../modules/operatorModule'
+import RiskModule from '../modules/riskModule'
+
+
+
+/** 登录后需要动态加入的本地路由 */
+const FrontRoutes = [
+  ...Dashboard,
+  // ...FormworkErection,
+  ...UserModule,
+  // ...WithdrawModule,
+  // ...AgentModule,
+  // ...OperatorModule,
+  ...RiskModule,
+]
+
+export default FrontRoutes

+ 116 - 0
src/router/reload.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="ok-loading">
+    <div class="ball-loader"> <span></span><span></span><span></span><span></span> </div>
+  </div>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  name: 'routerReload',
+  setup() {}
+});
+</script>
+
+<style lang="scss">
+.ok-loading {
+  width: calc(100% - 30px);
+  height: calc(100% - 30px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color:  var(--system-page-background);
+  margin: 15px;
+}
+.ok-loading.close {
+  animation: close 1s;
+  -webkit-animation: close 1s;
+  animation-fill-mode: forwards;
+}
+.ball-loader {
+
+}
+.ball-loader > span,
+.signal-loader > span {
+  background-color: var(--system-primary-color);
+  display: inline-block;
+}
+
+.ball-loader > span:nth-child(1),
+.ball-loader.sm > span:nth-child(1),
+.signal-loader > span:nth-child(1),
+.signal-loader.sm > span:nth-child(1) {
+  -webkit-animation-delay: 0s;
+  animation-delay: 0s;
+}
+.ball-loader > span:nth-child(2),
+.ball-loader.sm > span:nth-child(2),
+.signal-loader > span:nth-child(2),
+.signal-loader.sm > span:nth-child(2) {
+  -webkit-animation-delay: 0.1s;
+  animation-delay: 0.1s;
+}
+.ball-loader > span:nth-child(3),
+.ball-loader.sm > span:nth-child(3),
+.signal-loader > span:nth-child(3),
+.signal-loader.sm > span:nth-child(3) {
+  -webkit-animation-delay: 0.15s;
+  animation-delay: 0.15s;
+}
+.ball-loader > span:nth-child(4),
+.ball-loader.sm > span:nth-child(4),
+.signal-loader > span:nth-child(4),
+.signal-loader.sm > span:nth-child(4) {
+  -webkit-animation-delay: 0.2s;
+  animation-delay: 0.2s;
+}
+.ball-loader > span {
+  width: 20px;
+  height: 20px;
+  margin: 0 3px;
+  border-radius: 50%;
+  transform: scale(0);
+  -ms-transform: scale(0);
+  -webkit-transform: scale(0);
+  animation: ball-load 1s ease-in-out infinite;
+  -webkit-animation: 1s ball-load ease-in-out infinite;
+}
+@-webkit-keyframes ball-load {
+  0% {
+    transform: scale(0);
+    -webkit-transform: scale(0);
+  }
+  50% {
+    transform: scale(1);
+    -webkit-transform: scale(1);
+  }
+  100% {
+    transform: scale(0);
+    -webkit-transform: scale(0);
+  }
+}
+@keyframes ball-load {
+  0% {
+    transform: scale(0);
+    -webkit-transform: scale(0);
+  }
+  50% {
+    transform: scale(1);
+    -webkit-transform: scale(1);
+  }
+  100% {
+    transform: scale(0);
+    -webkit-transform: scale(0);
+  }
+}
+@keyframes close {
+  0% {
+    opacity: 1;
+    /*display: block;*/
+  }
+  100% {
+    opacity: 0;
+    /*display: none;*/
+  }
+}
+</style>

+ 29 - 0
src/store/index.js

@@ -0,0 +1,29 @@
+import { createStore, createLogger } from 'vuex'
+import Persistent from './plugins/persistent'
+const debug = import.meta.env.MODE !== 'production'
+const files= import.meta.globEager('./modules/*.js')
+
+let modules = {}
+Object.keys(files).forEach((c) => {
+  const module = files[c].default
+  const moduleName = c.replace(/^\.\/(.*)\/(.*)\.\w+$/, '$2')
+  modules[moduleName] = module
+})
+
+// 这是一个vuex本地存储插件,默认把vuex所有数据都做存储了
+// local代表存储在localStorage里面,进行永久存储
+// session代表存储在sessionStorage里面,进行临时存储
+// 都接收Modules的文件名数组,如:['app', 'keepAlive', 'user']
+// 用户相关的数据建议直接存储在local里面,session里面会导致打开新窗口时获取不到token值,因为session只针对当前会话
+const persistent = Persistent({ key: 'vuex', modules, modulesKeys: {
+  local: Object.keys(modules),
+  session: []
+} })
+
+export default createStore({
+  modules: {
+    ...modules
+  },
+  strict: debug,
+  plugins: debug ? [createLogger(), persistent] : [persistent]
+})

+ 44 - 0
src/store/modules/app.js

@@ -0,0 +1,44 @@
+const state = () => ({
+  isCollapse: false, // 侧边栏是否收缩展示
+  contentFullScreen: false, // 内容是否可全屏展示
+  showLogo: true, // 是否显示Logo
+  fixedTop: false, // 是否固定顶部, todo,暂未使用
+  showTabs: true, // 是否显示导航历史
+  expandOneMenu: true, // 一次是否只能展开一个菜单
+  elementSize: 'default', // element默认尺寸,支持官网'large / default /small'小参数
+  lang: '', // 默认采用的国际化方案,初次进入,采用浏览器当前设置的语言,默认采用中文
+  theme: {
+    state: {
+      style: 'default',
+      primaryColor: '#409eff',
+      menuType: 'side'
+    }
+  },
+  menuList: []
+})
+
+// mutations
+const mutations = {
+  isCollapseChange(state, type) {
+    state.isCollapse = type
+  },
+  contentFullScreenChange(state, type) {
+    state.contentFullScreen = type
+  },
+  menuListChange(state, arr) {
+    state.menuList = arr
+  },
+  stateChange(state, option) {
+    state[option.name] = option.value
+  }
+}
+
+// actions
+const actions = {}
+
+export default {
+  namespaced: true,
+  state,
+  actions,
+  mutations
+}

+ 40 - 0
src/store/modules/keepAlive.js

@@ -0,0 +1,40 @@
+const state = () => ({
+  keepAliveComponentsName: [] // 需要缓存的组件名称
+})
+
+// mutations
+const mutations = {
+  // 重置,Push, splice keep-alive对象
+  setKeepAliveComponentsName(state, nameArr) {
+    state.keepAliveComponentsName = nameArr
+  },
+  addKeepAliveComponentsName(state, name) {
+    state.keepAliveComponentsName.push(name)
+  },
+  delKeepAliveComponentsName(state, name) {
+    const key = state.keepAliveComponentsName.indexOf(name)
+    if (key !== -1) {
+      state.keepAliveComponentsName.splice(key, 1)
+      console.log(state.keepAliveComponentsName)
+    }
+  }
+}
+
+const getters = {
+  keepAliveComponentsName(state) {
+    return state.keepAliveComponentsName
+  }
+}
+
+// actions
+const actions = {
+
+}
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  actions,
+  mutations
+}

+ 74 - 0
src/store/modules/user.js

@@ -0,0 +1,74 @@
+import { loginApi, getInfoApi, loginOutApi } from '@/api/user'
+import { login, getUserInfo, logout } from '@/api/login.js'
+const state = () => ({
+  token: '', // 登录token
+  info: {},  // 用户信息
+})
+
+// getters
+const getters = {
+  token(state) {
+    return state.token
+  }
+}
+
+// mutations
+const mutations = {
+  tokenChange(state, token) {
+    state.token = token
+  },
+  infoChange(state, info) {
+    state.info = info
+  }
+}
+
+// 登录验证保存token
+const actions = {
+  login({ commit, dispatch }, params) {
+    return new Promise((resolve, reject) => {
+      login(params).then(res => {
+        commit('tokenChange', res.data.access_token)
+        dispatch('getInfo', { token: res.data.access_token }).then(infoRes => {
+          resolve(res.data.access_token)
+        }).catch((err)=> {
+          reject(err)
+        })
+
+      }).catch(err => {
+        reject(err)
+      })
+    })
+  },
+
+  // 获取用户登录信息
+  getInfo({ commit }, params) {
+    return new Promise((resolve, reject) => {
+      getUserInfo(params).then(res => {
+        commit('infoChange', res.data)
+        resolve(res.data)
+      })
+    })
+  },
+
+  // 退出登录
+  loginOut({ commit }) {
+    logout().then(res => {
+
+    }).catch(error => {
+
+    }).finally(() => {
+      localStorage.removeItem('tabs')
+      localStorage.removeItem('vuex')
+      sessionStorage.removeItem('vuex')
+      location.reload()
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  actions,
+  getters,
+  mutations
+}

+ 39 - 0
src/store/plugins/persistent.js

@@ -0,0 +1,39 @@
+const exclude = ['actions', 'getters', 'mutations', 'namespaced']
+export default function Persistent({ key, modules, modulesKeys }) {
+  return (store) => {
+    const localOldState = JSON.parse(localStorage.getItem(key) || '{}')
+    const sessionOldState = JSON.parse(sessionStorage.getItem(key) || '{}')
+    let oldState = {}
+    Object.assign(oldState, localOldState, sessionOldState)
+    if (Object.keys(oldState).length > 0) {
+      for (const oldKey in oldState) {
+        modules[oldKey] = oldState[oldKey]
+      }
+      store.replaceState(modules)
+    }
+    store.subscribe((mutation, state) => {
+      // 判断是否需要缓存数据至localStorage
+      if (modulesKeys.local.length > 0) {
+        const localData = setData(store.state, modulesKeys.local)
+        localStorage.setItem(key, JSON.stringify(localData))
+      } else {
+        localStorage.removeItem(key)
+      }
+      // 判断是否需要缓存数据至sessionStorage
+      if (modulesKeys.session.length > 0) {
+        const sessionData = setData(store.state, modulesKeys.session)
+        sessionStorage.setItem(key, JSON.stringify(sessionData))
+      } else {
+        sessionStorage.removeItem(key)
+      }
+    })
+  }
+}
+
+function setData(state, module) {
+  let data = {}
+  for (const i of module) {
+    data[i] = state[i]
+  }
+  return data
+}

+ 130 - 0
src/theme/index.js

@@ -0,0 +1,130 @@
+export const style = {
+  'default': {
+    name: '默认菜单风格',
+    menu: {
+      textColor: '#bfcbd9',
+      background: '#181f31',
+      childrenBackground: '#1f2d3d',
+      hoverBackground: '#203448',
+      submenuActiveColor: '#fff'
+    },
+    logo: {
+      color: '#f1f1f1',
+      background: '#263445'
+    },
+    header: {
+      background: '#fff',
+      textColor: '#303133',
+      itemHoverColor: 'rgba(0,0,0,.06)',
+      breadcrumbTextColor: '#606266',
+      borderColor: '#d8dce5',
+      tabBackground: '#fff'
+    },
+    container: {
+      background: '#f0f2f5',
+      mainBackground: '#fff'
+    },
+    page: {
+      background: '#fff',
+      color: '#303133',
+      tipColor: 'rgba(0, 0, 0, 0.45)',
+      borderColor: '#ebeef5'
+    }
+  },
+  'light': {
+    name: '亮色菜单风格',
+    menu: {
+      textColor: '#272727',
+      background: '#fff',
+      childrenBackground: '#fff',
+      hoverBackground: '#f1f1f1',
+      submenuActiveColor: 'var(--system-primary-color)'
+    },
+    logo: {
+      color: '#000',
+      background: '#fff'
+    },
+    header: {
+      background: '#fff',
+      textColor: '#303133',
+      itemHoverColor: 'rgba(0,0,0,.025)',
+      breadcrumbTextColor: '#606266',
+      borderColor: '#d8dce5',
+      tabBackground: '#fff'
+    },
+    container: {
+      background: '#f0f2f5',
+      mainBackground: '#fff'
+    },
+    page: {
+      background: '#fff',
+      color: '#303133',
+      tipColor: 'rgba(0, 0, 0, 0.45)',
+      borderColor: '#ebeef5'
+    }
+  },
+  'chinese': {
+    name: '中国水墨风',
+    menu: {
+      textColor: '#c7c7c7',
+      background: '#232323',
+      childrenBackground: '#292929',
+      hoverBackground: '#1d1d1d',
+      submenuActiveColor: 'var(--system-primary-color)'
+    },
+    logo: {
+      color: '#fff',
+      background: '#232323'
+    },
+    header: {
+      background: '#f1f0ed',
+      textColor: '#303133',
+      itemHoverColor: 'rgba(0,0,0,.025)',
+      breadcrumbTextColor: '#606266',
+      borderColor: '#d8dce5',
+      tabBackground: 'rgba(216, 216, 216, 0.51)'
+    },
+    container: {
+      background: 'rgba(255, 255, 255, 0.92)',
+      mainBackground: 'rgba(255, 255, 255, 0.92)'
+    },
+    page: {
+      background: 'rgba(255, 255, 255, 0.92)',
+      color: '#303133',
+      tipColor: 'rgba(0, 0, 0, 0.45)',
+      borderColor: '#ebeef5'
+    }
+  },
+  'dark': {
+    name: '暗色菜单风格',
+    menu: {
+      textColor: '#bbb',
+      background: '#18181c',
+      childrenBackground: '#18181c',
+      hoverBackground: '#000',
+      submenuActiveColor: '#fff'
+    },
+    logo: {
+      color: '#fff',
+      background: '#18181c'
+    },
+    header: {
+      background: '#18181c',
+      textColor: '#e3e3e4',
+      itemHoverColor: '#000',
+      breadcrumbTextColor: '#fff',
+      borderColor: '#3e3e3e',
+      tabBackground: '#1b1b1b'
+    },
+    container: {
+      background: '#000',
+      mainBackground: '#18181c'
+    },
+    page: {
+      background: '#18181c',
+      color: '#c7c7c7',
+      tipColor: 'rgba(255, 255, 255, 0.45)',
+      borderColor: '#3e3e3e'
+    }
+  }
+}

+ 52 - 0
src/theme/index.scss

@@ -0,0 +1,52 @@
+@use './modules/dark.scss';
+:root {
+  // 主题色
+  --system-primary-color: #409eff; // 可做背景色和文本色,用做背景色时,需要和--system-primary-text-color配合使用,避免文件颜色和主题色冲突
+  --system-primary-text-color: #fff; // 主题色作为背景色时使用
+
+  // logo颜色相关
+  --system-logo-color: #f1f1f1;
+  --system-logo-background: #263445;
+
+  // 菜单颜色相关
+  --system-menu-text-color: #bfcbd9;
+  --system-menu-background: #181f31;
+  --system-menu-children-background: #1f2d3d;
+  --system-menu-submenu-active-color: #fff; 
+  --system-menu-hover-background: #203448;
+
+  // header区域
+  --system-header-background: #fff;
+  --system-header-text-color: #303133;
+  --system-header-breadcrumb-text-color: #606266;
+  --system-header-item-hover-color: rgba(0,0,0,.06);
+  --system-header-border-color: #d8dce5;
+  --system-header-tab-background: #fff;
+
+  // contaier区域,父框架
+  --system-container-background: #f0f2f5;
+  --system-container-main-background: #fff;
+
+  // 页面区域, 这一块是你在自己写的文件中使用主题,核心需要关注的地方
+  --system-page-background: #fff; // 主背景
+  --system-page-color: #303133; // 主要的文本颜色
+  --system-page-tip-color: rgba(0, 0, 0, 0.45); // 协助展示的文本颜色
+  --system-page-border-color: #ebeef5; // 通用的边框配置色,便于主题扩展
+  
+  // element主题色修改
+  --el-color-primary: var(--system-primary-color);
+}
+
+// 进度条颜色修改为主题色
+body #nprogress .bar {
+  background-color: var(--system-primary-color);
+}
+body #nprogress .peg {
+  box-shadow: 0 0 10px var(--system-primary-color), 0 0 5px var(--system-primary-color);
+}
+body #nprogress .spinner-icon {
+  border-top-color: var(--system-primary-color);
+  border-left-color: var(--system-primary-color);
+}
+
+// @import './modules/dark.scss';

+ 7 - 0
src/theme/modules/chinese/index.scss

@@ -0,0 +1,7 @@
+// 带有本地背景图片的主题需要在main.ts里面主动引入,否则无法正常解析
+[data-theme='chinese'] {
+  .el-main {
+    background-image: url("./screen.png");
+    background-size: 100% 100%;
+  }
+}

TEMPAT SAMPAH
src/theme/modules/chinese/screen.png


+ 23 - 0
src/theme/modules/dark.scss

@@ -0,0 +1,23 @@
+[data-theme="dark"] {
+  // 通用
+  p, h1, h2, h3, h4, h5, h6, article {
+    color: var(--system-page-color);
+  }
+  .el-tree {
+    background-color: var(--system-page-background);
+    .el-tree-node__content:hover {
+      background-color: #272727;
+    }
+    --el-color-primary-light-9: #272727;
+  }
+  .el-card {
+    background-color: var(--system-page-background);
+    color: var(--system-page-color);
+    border-color: var(--system-page-border-color);
+    .el-card__header {
+      border-color: var(--system-page-border-color);
+    }
+  }
+  // 页面内部样式修改
+  
+}

+ 37 - 0
src/utils/index.js

@@ -0,0 +1,37 @@
+
+
+/**
+ * UTC时间字符串转为北京时间
+ * @param {*} utcStr 2025-05-15T02:16:27.000+00:00
+ * @returns 2025-05-15 10:16:27
+ */
+export function convertUTCToBeijing(utcStr) {
+    if (utcStr) {
+        const date = new Date(utcStr);
+
+        // 北京时间 = UTC 时间
+        date.setHours(date.getHours());
+
+        const pad = (n) => String(n).padStart(2, '0');
+        const y = date.getFullYear();
+        const m = pad(date.getMonth() + 1);
+        const d = pad(date.getDate());
+        const hh = pad(date.getHours());
+        const mm = pad(date.getMinutes());
+        const ss = pad(date.getSeconds());
+
+        return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
+    } else {
+        return utcStr
+    }
+}
+
+/**
+ * 转成小写字符
+ * userStatus 转成 user_status
+ * @param {*} str 
+ * @returns 
+ */
+export function camelToSnake(str) {
+    return str.replace(/([A-Z])/g, '_$1').toLowerCase();
+}

+ 12 - 0
src/utils/system/nprogress.js

@@ -0,0 +1,12 @@
+import NProgress from "nprogress"
+import "nprogress/nprogress.css"
+
+NProgress.configure({
+  easing: 'ease', // 动画方式    
+  speed: 500, // 递增进度条的速度    
+  showSpinner: true, // 是否显示加载ico    
+  trickleSpeed: 200, // 自动递增间隔    
+  minimum: 0.3 // 初始化时的最小百分比
+})
+
+export default NProgress

+ 87 - 0
src/utils/system/request.js

@@ -0,0 +1,87 @@
+import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'
+import store from '@/store'
+import { ElMessage } from 'element-plus'
+const baseURL = import.meta.env.VITE_BASE_URL
+
+const service = axios.create({
+  baseURL: baseURL,
+  timeout: 10000
+})
+
+// 请求前的统一处理
+service.interceptors.request.use((config) => {
+  const token = store.state.user.token
+  // JWT鉴权处理
+  if (store.getters['user/token']) {
+    config.headers["token"] = store.state.user.token;
+    config.headers['Authorization'] = 'Bearer ' + token
+  }
+  return config
+},
+  (error) => {
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+service.interceptors.response.use(
+  (response) => {
+    // 如果是 Blob(比如验证码图片),直接返回完整 response
+    const contentType = response.headers['content-type']
+    if (response.config.responseType === 'blob' || contentType?.includes('application/octet-stream') || contentType?.includes('image')) {
+      return response
+    }
+
+    const res = response.data
+    /*  if (res.code === 200) {
+       return res
+     } else {
+       showError(res)
+       return Promise.reject(res)
+     } */
+    if (res.Authorization) {
+      return res;
+    } else if (res.status === 200 || res.code === 200) {
+      return res;
+    } else {
+      showError(res);
+      return Promise.reject(res);
+    }
+  },
+  (error) => {
+    console.log(error)
+    const badMessage = error.message || error
+    const code = parseInt(badMessage.toString().replace('Error: Request failed with status code ', ''))
+    showError({ code, message: badMessage })
+    return Promise.reject(error)
+  }
+)
+
+// 错误处理
+function showError(error) {
+  // token过期,清除本地数据,并跳转至登录页面
+  if (error.code === 403) {
+    // to re-login
+    store.dispatch('user/loginOut')
+  }
+  else if (error.code === 1024) {
+    ElMessage({
+      message: "token过期,请重新登录",
+      type: "error",
+      duration: 1000,
+    });
+    // to re-login
+    setTimeout(() => {
+      store.dispatch("user/loginOut");
+    }, 1000);
+  } else {
+    ElMessage({
+      message: error.msg || error.message || '服务异常',
+      type: 'error',
+      duration: 3 * 1000
+    })
+  }
+
+}
+
+export default service

+ 15 - 0
src/utils/system/statistics.js

@@ -0,0 +1,15 @@
+// 百度统计代码,需自行更换
+export function baidu() {
+  const script = document.createElement('script')
+  script.type = 'text/javascript'
+  script.text = `
+    var _hmt = _hmt || [];
+    (function() {
+      var hm = document.createElement("script");
+      hm.src = "https://hm.baidu.com/hm.js?bd78bc908e66174e7dde385bf37cb4c1";
+      var s = document.getElementsByTagName("script")[0]; 
+      s.parentNode.insertBefore(hm, s);
+    })();
+  `
+  document.getElementsByTagName('head')[0].appendChild(script)
+}

+ 5 - 0
src/utils/system/title.js

@@ -0,0 +1,5 @@
+import { systemTitle } from '@/config'
+
+export function changeTitle(name) {
+  document.title = `${name}-${systemTitle}`
+}

+ 46 - 0
src/utils/tab/index.js

@@ -0,0 +1,46 @@
+/*
+ * @Date: 2022-09-25 20:05:01
+ * @Description: tab面板的操作方法
+ */
+
+/** 关闭当前标签 */
+export const closeCurrentTab = (nextPath) => {
+  /** 拿到tab组件 */
+  const tab = document.getElementById('vueAdminBoxTabCloseSelf')
+  if (nextPath) {
+    /** 设置下一个tab的路径 */
+    tab?.setAttribute('nextpath', nextPath)
+  }
+  /** 触发tab事件点击 */
+  tab?.click()
+  if (nextPath) {
+    setTimeout(() => {
+      /** 清除下一个tab的路径 */
+      tab?.removeAttribute('nextpath')
+    }, 100)
+  }
+}
+
+/** 关闭其他标签 */
+export const closeOtherTab = () => {
+  /** 拿到tab组件 */
+  const tab = document.getElementById('vueAdminBoxTabCloseOther')
+  /** 触发tab事件点击 */
+  tab?.click()
+}
+
+/** 关闭所有标签 */
+export const closeAllTab = () => {
+  /** 拿到tab组件 */
+  const tab = document.getElementById('vueAdminBoxTabCloseAll')
+  /** 触发tab事件点击 */
+  tab?.click()
+}
+
+/** 刷新当前标签 */
+export const refreshCurrentTab = () => {
+  /** 拿到tab组件 */
+  const tab = document.getElementById('vueAdminBoxTabRefresh')
+  /** 触发tab事件点击 */
+  tab?.click()
+}

+ 9 - 0
src/views/main/agentModule/index.vue

@@ -0,0 +1,9 @@
+<template></template>
+  <div class="layout-container">
+    代理模块
+  </div>
+</template>
+
+<script setup></script>
+
+<style lang="scss" scoped></style>

+ 35 - 0
src/views/main/dashboard/components/card/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <div class="card-list">
+    <Row v-for="row in list" :key="row.id" :row="row" />
+  </div>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+import Row from './row.vue'
+export default defineComponent({
+  components: {
+    Row
+  },
+  setup() {
+    const list = [
+      { id: 1, name: '总广告数', data: '12800', color: '#4e73df', icon: 'sfont system-yonghu' },
+      { id: 2, name: '封禁人数', data: '30', color: '#1cc88a', icon: 'sfont system-xiaoxi' },
+      { id: 3, name: '提现笔数', data: '42567', color: '#36b9cc', icon: 'sfont system-shuliang_mianxing' },
+      { id: 4, name: '提现金额', data: '1168,000', color: '#f6c23e', icon: 'sfont system-jindutiaoshouyidaozhang' }
+    ]
+    return {
+      list
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .card-list {
+    width: calc(100% + 20px);
+    margin-left: -10px;
+    display: flex;
+    flex-wrap: wrap;
+  }
+</style>

+ 82 - 0
src/views/main/dashboard/components/card/row.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="card" :style="{ 'borderColor': row.color }">
+    <div class="card-left">
+      <div class="name">{{ row.name }}</div>
+      <div class="detail">{{ row.data }}</div>
+    </div>
+    <div class="card-right">
+      <i :class="row.icon"></i>
+    </div>
+  </div>
+</template>
+
+<script lang="js">
+import { defineComponent } from 'vue'
+export default defineComponent({
+  props: {
+    row: {
+      type: Object,
+      default: () => {
+        return {
+
+        }
+      }
+    }
+  },
+  setup(props) {
+
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .card{
+    border-radius: 4px;
+    overflow: hidden;
+    border-left: 6px solid;
+    height: 96px;
+    display: inline-flex;
+    align-items: center;
+    justify-content: space-between;
+    width: calc(25% - 26px);
+    background-color: var(--system-page-background);
+    margin: 10px;
+    box-shadow: 3px 3px 10px 0 rgba(58, 59, 69, 0.15);
+    &-left{
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: flex-start;
+      padding-left: 20px;
+      .name {
+        line-height: 18px;
+        color: var(--system-page-tip-color);
+        font-size: 16px;
+        margin-bottom: 14px;
+      }
+      .detail {
+        font-size: 20px;
+        font-weight: bold;
+        color: var(--system-page-color);
+      }
+    }
+    &-right {
+      padding-right: 15px;
+      i {
+        font-size: 40px;
+        font-weight: 900;
+        color: #e6e8ef;
+      }
+    }
+  }
+  @media screen and ( max-width: 1200px ) {
+    .card {
+      width: calc(50% - 26px);
+    }
+  }
+  @media screen and ( max-width: 500px ) {
+    .card {
+      width: calc(100% - 26px);
+    }
+  }
+</style>

+ 44 - 0
src/views/main/dashboard/components/charts/barChart.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="box">
+    <Chart :option="options" />
+  </div>
+</template>
+
+<script lang="js">
+import { defineComponent, onBeforeUnmount, reactive } from 'vue'
+import Chart from '@/components/charts/index.vue'
+import option from './modules/bar'
+export default defineComponent({
+  components: {
+    Chart
+  },
+  setup() {
+    let timer = null;
+    const datar = [100,200,300,400,500,600,700,800,900,1000,1100,1200]
+    const options = reactive(option)
+    // 模拟异步请求
+    timer = setTimeout(() => {
+      options.series[0].data = datar
+    },1000)
+    // 组件销毁时清除定时器
+    onBeforeUnmount(() => {
+      clearInterval(timer)
+      timer = null;
+    })
+    return {
+      options
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+  .box {
+    margin: 10px auto 0;
+    width: calc(100% - 40px);
+    height: 400px;
+    background: var(--system-page-background);
+    padding: 20px;
+    overflow: hidden;
+  }
+</style>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini