Browse Source

feat: init

liming 5 months ago
commit
4efd4b36b3
100 changed files with 10018 additions and 0 deletions
  1. 1 0
      .env.dev
  2. 25 0
      .gitignore
  3. 3 0
      .vscode/extensions.json
  4. 7 0
      README.md
  5. 177 0
      index.html
  6. 11 0
      jsconfig.json
  7. 31 0
      package.json
  8. 396 0
      public/cdn/es-module-shims.wasm.js
  9. 6 0
      public/cdn/runtime-dom.esm-browser.js
  10. 6 0
      public/cdn/server-renderer.esm-browser.js
  11. 1 0
      public/vite.svg
  12. 92 0
      src/App.vue
  13. 111 0
      src/api/axios.js
  14. 12 0
      src/api/path/component.api.js
  15. 34 0
      src/api/path/dictionary.js
  16. 11 0
      src/api/path/login.api.js
  17. 20 0
      src/api/path/star.api.js
  18. 80 0
      src/api/path/system.api.js
  19. 46 0
      src/assets/css/global.less
  20. 27 0
      src/assets/css/theme.less
  21. 539 0
      src/assets/iconsvg/demo.css
  22. 2051 0
      src/assets/iconsvg/demo_index.html
  23. 339 0
      src/assets/iconsvg/iconfont.css
  24. 0 0
      src/assets/iconsvg/iconfont.js
  25. 576 0
      src/assets/iconsvg/iconfont.json
  26. BIN
      src/assets/iconsvg/iconfont.ttf
  27. BIN
      src/assets/iconsvg/iconfont.woff
  28. BIN
      src/assets/iconsvg/iconfont.woff2
  29. BIN
      src/assets/images/error-image.png
  30. BIN
      src/assets/images/login-banner.png
  31. 1 0
      src/assets/vite.svg
  32. 1 0
      src/assets/vue.svg
  33. 176 0
      src/components/FindHead/index.vue
  34. 30 0
      src/components/Layout/Layout.vue
  35. 6 0
      src/components/Layout/components/index.js
  36. 303 0
      src/components/Layout/components/layout/blendLeft.vue
  37. 110 0
      src/components/Layout/components/layoutHeader/index.vue
  38. 33 0
      src/components/Layout/components/subMenu/index.vue
  39. 113 0
      src/components/MonacoEditor/index.vue
  40. 43 0
      src/components/Svg-icon/SvgIcon.vue
  41. 2 0
      src/hooks/index.js
  42. 26 0
      src/hooks/useLang.hook.js
  43. 12 0
      src/hooks/useTheme.hook.js
  44. 55 0
      src/i18n/en/dataPackage.js
  45. 63 0
      src/i18n/en/form.js
  46. 11 0
      src/i18n/en/global.js
  47. 15 0
      src/i18n/en/index.js
  48. 3 0
      src/i18n/en/login.js
  49. 54 0
      src/i18n/en/plan.js
  50. 85 0
      src/i18n/en/supplier.js
  51. 38 0
      src/i18n/index.js
  52. 55 0
      src/i18n/th/dataPackage.js
  53. 63 0
      src/i18n/th/form.js
  54. 11 0
      src/i18n/th/global.js
  55. 15 0
      src/i18n/th/index.js
  56. 3 0
      src/i18n/th/login.js
  57. 74 0
      src/i18n/th/plan.js
  58. 85 0
      src/i18n/th/supplier.js
  59. 208 0
      src/i18n/zh/customer.js
  60. 22 0
      src/i18n/zh/customerPackage.js
  61. 56 0
      src/i18n/zh/dataPackage.js
  62. 86 0
      src/i18n/zh/form.js
  63. 12 0
      src/i18n/zh/global.js
  64. 19 0
      src/i18n/zh/index.js
  65. 3 0
      src/i18n/zh/login.js
  66. 75 0
      src/i18n/zh/plan.js
  67. 85 0
      src/i18n/zh/supplier.js
  68. 49 0
      src/main.js
  69. 23 0
      src/router/index.js
  70. 37 0
      src/router/router.guards.js
  71. 69 0
      src/router/router.system.js
  72. 70 0
      src/router/router.update.js
  73. 10 0
      src/settings/designSetting.js
  74. 11 0
      src/settings/pagination.js
  75. 9 0
      src/store/index.js
  76. 29 0
      src/store/modules/designStore.js
  77. 30 0
      src/store/modules/langStore.js
  78. 132 0
      src/store/modules/systemStore.js
  79. 79 0
      src/style.css
  80. 0 0
      src/utils/components.js
  81. 63 0
      src/utils/crypto.js
  82. 5 0
      src/utils/index.js
  83. 18 0
      src/utils/router.js
  84. 30 0
      src/utils/storage.js
  85. 61 0
      src/utils/utils.js
  86. 53 0
      src/views/exception/errors.vue
  87. 118 0
      src/views/login/index.vue
  88. 129 0
      src/views/login/login-form.vue
  89. 155 0
      src/views/overflowalarm/index.vue
  90. 171 0
      src/views/plan-management/NewFeeForm.vue
  91. 176 0
      src/views/plan-management/index.vue
  92. 114 0
      src/views/staging/index.vue
  93. 128 0
      src/views/system/dictionary/DictionaryForm.vue
  94. 95 0
      src/views/system/dictionary/config.js
  95. 118 0
      src/views/system/dictionary/index.vue
  96. 50 0
      src/views/system/menu/config.js
  97. 550 0
      src/views/system/menu/index.vue
  98. 24 0
      src/views/system/role/config.js
  99. 435 0
      src/views/system/role/index.vue
  100. 323 0
      src/views/system/role/modalMode.vue

+ 1 - 0
.env.dev

@@ -0,0 +1 @@
+VITE_PRO_PATH=""

+ 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?
+yarn.lock

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# 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.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously Volar) and disable Vetur

+ 177 - 0
index.html

@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue</title>
+    <style type="text/css">
+      #Loading {
+        width: 100vw;
+        height: 100vh;
+        top: 50%;
+        left: 50%;
+        position: absolute;
+        transform: translateY(-50%) translateX(-50%);
+        z-index: 100;
+        filter: contrast(20);
+        background: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        display: flex;
+        flex-direction: column;
+      }
+      .breeding-rhombus-spinner {
+        height: 65px;
+        width: 65px;
+        position: relative;
+        transform: rotate(45deg) scale(1.5);
+      }
+
+      .breeding-rhombus-spinner,
+      .breeding-rhombus-spinner * {
+        box-sizing: border-box;
+        filter: blur(2px);
+      }
+
+      .breeding-rhombus-spinner .rhombus {
+        height: calc(65px / 7.5);
+        width: calc(65px / 7.5);
+        top: calc(65px / 2.3077);
+        left: calc(65px / 2.3077);
+        background-color: #e31937;
+        position: absolute;
+        animation-duration: 2s;
+        animation-iteration-count: infinite;
+      }
+
+      .breeding-rhombus-spinner .rhombus:nth-child(2n) {
+        margin-right: 0;
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-1 {
+        animation-name: breeding-rhombus-spinner-animation-child-1;
+        animation-delay: calc(100ms * 1);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-2 {
+        animation-name: breeding-rhombus-spinner-animation-child-2;
+        animation-delay: calc(100ms * 2);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-3 {
+        animation-name: breeding-rhombus-spinner-animation-child-3;
+        animation-delay: calc(100ms * 3);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-4 {
+        animation-name: breeding-rhombus-spinner-animation-child-4;
+        animation-delay: calc(100ms * 4);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-5 {
+        animation-name: breeding-rhombus-spinner-animation-child-5;
+        animation-delay: calc(100ms * 5);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-6 {
+        animation-name: breeding-rhombus-spinner-animation-child-6;
+        animation-delay: calc(100ms * 6);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-7 {
+        animation-name: breeding-rhombus-spinner-animation-child-7;
+        animation-delay: calc(100ms * 7);
+      }
+
+      .breeding-rhombus-spinner .rhombus.child-8 {
+        animation-name: breeding-rhombus-spinner-animation-child-8;
+        animation-delay: calc(100ms * 8);
+      }
+
+      .breeding-rhombus-spinner .rhombus.big {
+        height: calc(65px / 3);
+        width: calc(65px / 3);
+        top: calc(65px / 3);
+        left: calc(65px / 3);
+        background-color: #e31937;
+        animation-duration: 2s;
+        animation: breeding-rhombus-spinner-animation-child-big 2s infinite;
+        animation-delay: 0.5s;
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-1 {
+        50% {
+          transform: translate(-325%, -325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-2 {
+        50% {
+          transform: translate(0, -325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-3 {
+        50% {
+          transform: translate(325%, -325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-4 {
+        50% {
+          transform: translate(325%, 0);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-5 {
+        50% {
+          transform: translate(325%, 325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-6 {
+        50% {
+          transform: translate(0, 325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-7 {
+        50% {
+          transform: translate(-325%, 325%);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-8 {
+        50% {
+          transform: translate(-325%, 0);
+        }
+      }
+
+      @keyframes breeding-rhombus-spinner-animation-child-big {
+        50% {
+          transform: scale(0.5);
+        }
+      }
+    </style>
+  </head>
+  <body>
+    <div id="Loading">
+      <div class="breeding-rhombus-spinner">
+        <div class="rhombus child-1"></div>
+        <div class="rhombus child-2"></div>
+        <div class="rhombus child-3"></div>
+        <div class="rhombus child-4"></div>
+        <div class="rhombus child-5"></div>
+        <div class="rhombus child-6"></div>
+        <div class="rhombus child-7"></div>
+        <div class="rhombus child-8"></div>
+        <div class="rhombus big"></div>
+      </div>
+    </div>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+    <script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.18.0.min.js"></script>
+  </body>
+</html>

+ 11 - 0
jsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@/*": ["src/*"]  // 将项目根目录配置别名为@
+    },
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["src/**/*.js", "src/**/*.ts", "src/**/*.vue", "src/**/*.tsx"],
+  "exclude": ["node_modules", "dist"]
+}

+ 31 - 0
package.json

@@ -0,0 +1,31 @@
+{
+  "name": "admin-web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@arco-design/web-vue": "^2.55.3",
+    "axios": "^1.7.2",
+    "crypto-js": "^4.2.0",
+    "dayjs": "^1.11.11",
+    "less": "^4.2.0",
+    "less-loader": "^12.2.0",
+    "lodash": "^4.17.21",
+    "monaco-editor": "^0.52.0",
+    "pinia": "^2.1.7",
+    "vue": "^3.4.21",
+    "vue-i18n": "^10.0.1",
+    "vue-router": "^4.3.2"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.4",
+    "fast-glob": "^3.3.2",
+    "vite": "^5.2.0",
+    "vite-plugin-svg-icons": "^2.0.1"
+  }
+}

File diff suppressed because it is too large
+ 396 - 0
public/cdn/es-module-shims.wasm.js


File diff suppressed because it is too large
+ 6 - 0
public/cdn/runtime-dom.esm-browser.js


File diff suppressed because it is too large
+ 6 - 0
public/cdn/server-renderer.esm-browser.js


+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 92 - 0
src/App.vue

@@ -0,0 +1,92 @@
+<template>
+  <a-config-provider 
+    :locale="locale"
+  >
+    <div class="public_local_loading" :class="{ 'hidden-loading': !systemStore.getLocalLoading }">
+      <div class="public_local_loading_bg">
+        <a-spin class="public_local_loading_logo" dot />
+      </div>
+    </div>
+    <router-view  v-if="isRouterActive"/>
+  </a-config-provider>
+</template>
+
+<script setup>
+import { ref, onMounted, nextTick, provide } from "vue"
+import { useDarkThemeHook, useLang } from '@/hooks'
+import { useSystemStore } from '@/store/modules/systemStore'
+
+const isRouterActive = ref(true)
+// 全局语言
+const { locale } = useLang()
+const systemStore = useSystemStore()
+
+// 暗黑主题
+useDarkThemeHook()
+
+provide('reloadRoutePage', () => {
+  isRouterActive.value = false
+  nextTick(() => {
+    isRouterActive.value = true
+  })
+})
+
+
+onMounted(() => {
+  const getLoadingNode = document.getElementById('Loading')
+  const { body } = document
+  if (getLoadingNode) {
+    body.removeChild(getLoadingNode)
+  }
+})
+</script>
+
+<style  lang="less" >
+body {
+  overflow: hidden;
+}
+// 加载loading css
+.public_local_loading {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 2000;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  .public_local_loading_bg {
+    width: 12rem;
+    height: 12rem;
+    position: sticky;
+    left: 50%;
+    top: 50%;
+    transform: translate3d(-50%, -50%, 0);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .public_local_loading_logo {
+    font-size: 2rem;
+    user-select: none;
+  }
+}
+.hidden-loading.public_local_loading {
+  opacity: 0;
+  z-index: -10 !important;
+  cursor: auto !important;
+}
+@keyframes r-loading {
+  0% {
+    transform: rotate(0deg);
+  }
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 111 - 0
src/api/axios.js

@@ -0,0 +1,111 @@
+
+import axios from 'axios'
+import Router from '@/router'
+import { Message, Notification } from '@arco-design/web-vue'
+import { useSystemStore } from '@/store/modules/systemStore'
+
+ 
+import { fn_logout } from '@/utils'
+
+
+// console.log(import.meta.env.BASE_URL)
+const axiosInstance = axios.create({
+  // baseURL: `${import.meta.env.PROD ? import.meta.env.VITE_PRO_PATH : ''}/api`,
+  baseURL: import.meta.env.BASE_URL + "api",
+  timeout: 300000,
+})
+
+
+const requestState = {
+  // 得到正确响应
+  success: [ 200],
+  // token 跳转
+  beOverdue: [886],
+  // 没有访问权限
+  NotAccessRight: [500],
+  // 异常code
+  exception: [400]
+
+}
+
+
+const pathArr = ['/api/user/login', '/api/logout']
+axiosInstance.interceptors.request.use(
+  (config) => {
+    const systemStore = useSystemStore()
+    systemStore.localLoading(true)
+   
+    // 在发送请求之前做些什么
+    if (!pathArr.includes(config.url)) {
+      const token = localStorage.getItem('token')
+      if (token && config.headers) {
+        config.headers['Authorization'] = token
+      }
+    }
+    return config
+  },(err) => {
+    const systemStore = useSystemStore()
+    systemStore.localLoading()
+    Promise.reject(err)
+  }
+)
+
+// 响应拦截器
+axiosInstance.interceptors.response.use(
+  (res) => {
+  const systemStore = useSystemStore()
+  systemStore.localLoading()
+  const { code, data, message:msg } = res.data
+  // 成功
+  if (code === 200) {
+    return Promise.resolve(res.data)
+  }
+
+  // 服务端错误信息
+  if(requestState.NotAccessRight.includes(code)){
+    Notification.warning({
+      title: '系统信息',
+      content: msg,
+    })
+    return Promise.reject(msg);
+  }
+
+  // 异常code
+  if(requestState.exception.includes(code)){
+    Notification.warning({
+      title: '系统提示',
+      content: msg,
+    })
+    return Promise.reject(msg);
+  }
+  //token失效
+  if(requestState.beOverdue.includes(code)){
+    fn_logout(Router)
+    Notification.warning({
+      title: '系统信息',
+      content: msg,
+      duration: 2000,
+    })
+    return Promise.reject(msg); 
+  }
+
+  return Promise.resolve(data)
+  },(err) => {
+    const systemStore = useSystemStore()
+    systemStore.localLoading()
+    const res = err['response']
+    if(err.code === "ERR_CANCELED") {
+      console.log('请求中断')
+      return
+    }
+    console.log("err.response-",err.response)
+    Notification.warning({
+      title: '系统信息',
+      content: err.response.data ? err.response.data.message : '',
+    })
+    Promise.reject(err)
+  }
+)
+
+export default axiosInstance
+

+ 12 - 0
src/api/path/component.api.js

@@ -0,0 +1,12 @@
+
+import service from '../axios'
+
+// 保存物料
+export function componentSave(param) {
+  return service.post('/admin/component/save', param)
+}
+
+// 获取物料
+export function componentList(param) {
+  return service.post('/admin/component/list', param)
+}

+ 34 - 0
src/api/path/dictionary.js

@@ -0,0 +1,34 @@
+import service from '../axios'
+
+// 获取字典列表
+export function getDictionaryList(param) {
+  return service.post('/admin/dic/list', param)
+}
+
+export const getDictionaryById = async (id) => {
+  // 模拟 API 调用延迟
+  await new Promise(resolve => setTimeout(resolve, 500));
+  return {
+    id,
+    label: `Dictionary ${id}`,
+    value: `Value ${id}`,
+    typeLabel: 'Type Label',
+    typeId: 'Type ID',
+    description: 'Description',
+    sortNo: 1,
+  };
+};
+
+export const addDictionary = async (data) => {
+  // 模拟 API 调用延迟
+  await new Promise(resolve => setTimeout(resolve, 500));
+  console.log('API: Adding dictionary item', data);
+  return { ...data, id: Date.now() };
+};
+
+export const updateDictionary = async (data) => {
+  // 模拟 API 调用延迟
+  await new Promise(resolve => setTimeout(resolve, 500));
+  console.log('API: Updating dictionary item', data);
+  return data;
+};

+ 11 - 0
src/api/path/login.api.js

@@ -0,0 +1,11 @@
+
+import service from '../axios'
+
+// * 登录
+export function loginApi(data) {
+  return service({
+    url: '/admin/system/login',
+    method: 'post',
+    data
+  })
+}

+ 20 - 0
src/api/path/star.api.js

@@ -0,0 +1,20 @@
+
+import service from '../axios'
+
+// 分销商
+// 新增
+export function distributorAdd(param) {
+  return service.post('/admin/star/setItem', param)
+}
+// list
+export function distributorList(param) {
+  return service.post('/admin/star/list', param)
+}
+// 编辑
+export function distributorEdit(param) {
+  return service.post('/admin/star/update', param)
+}
+// 删除
+export function distributorDelete(param) {
+  return service.post('/admin/star/delete', param)
+}

+ 80 - 0
src/api/path/system.api.js

@@ -0,0 +1,80 @@
+
+import service from '../axios'
+
+// 获取菜单
+
+export function systemUserInfoMenu(param) {
+  return service.get('/admin/system/userInfoMenu', param)
+}
+/**
+ * 新增用户
+ */
+export function userAdd(param) {
+  return service.post('/admin/user/register', param)
+}
+/**
+ * 用户列表
+ */
+export function userList(param) {
+  return service.post('/admin/user/list', param)
+}
+// 删除
+export function deleteUserItem(param) {
+  return service.post('/admin/user/deleteUser', param)
+} 
+// 更新
+export function updateUserItem(param) {
+  return service.post('/admin/user/updateUser', param)
+} 
+// 状态更新
+export function updateUserState(param){
+  return service.post('/admin/user/updateUserState', param) 
+}
+// menu
+// 菜单添加
+export function systemSetMenu(param) {
+  return service.post('/admin/system/setMenu', param)
+}
+// 菜单查询
+export function systemFinMenuAll(param) {
+  return service.post('/admin/system/finMenuAll', param)
+}
+// 删除菜单
+export function systemDeleteMenu(param) {
+  return service.post('/admin/system/deleteMenu', param)
+}
+// 更新菜单
+export function systemUpdateMenu(param) {
+  return service.post('/admin/system/updateMenu', param)
+}
+
+
+// role
+//插入role
+export function systemSetRole(param) {
+  return service.post('/admin/system/setRole', param)
+}
+// 获取
+export function systemFindRoleList(param) {
+  return service.post('/admin/system/findRoleList', param)
+}
+// 修改 
+export function systemUpdateRole(param) {
+  return service.post('/admin/system/updateRole', param)
+} 
+// 删除角色 
+export function systemDeleteRole(param) {
+  return service.post('/admin/system/deleteRole', param)
+} 
+// 查询角色下的账号
+export function systemFindRoleOrUser(param) {
+  return service.post('/admin/system/findRoleOrUser', param) 
+}
+// 解绑
+export function systemRelieveRoleUserById(param) {
+  return service.post('/admin/system/relieveRoleUserById', param) 
+}
+// * 获取云信息
+export function getSTSInfo(params) {
+  return service.get('/admin/system/getSTSInfo', params)
+}

+ 46 - 0
src/assets/css/global.less

@@ -0,0 +1,46 @@
+.fn-headTitleDiv(@params: 16px) {
+  // 页面标题样式
+  width: 100%;
+  font-size: @params;
+  font-weight: bold;
+  color: @text_color_1;
+  background-color: @bg_color_4;
+  border-bottom: 1px solid @black_3;
+  display: flex;
+  align-items: center;
+  padding: 1rem 0;
+  position: sticky;
+  top: 0;
+  z-index: 11;
+
+  .tip {
+    font-weight: bold;
+    font-size: 12px;
+    color: #9696A0;
+    margin-left: 1rem;
+    margin-top: 5px;
+  }
+
+  .search-input {
+    width: 14rem;
+    margin-left: auto;
+  }
+}
+
+.container {
+ 
+  flex-direction: column;
+  .head-title {
+    margin-bottom: 1rem;
+    .fn-headTitleDiv();
+  }
+}
+
+a {
+  color: 	rgb(var(--arcoblue-4));
+  text-decoration: none;
+  &:hover {
+    color: rgb(var(--arcoblue-6));
+    cursor: pointer;
+  }
+}

+ 27 - 0
src/assets/css/theme.less

@@ -0,0 +1,27 @@
+@layout-header-height: #fff;
+// 背景 边框
+@layout-split-color: var(--color-neutral-2);
+
+
+// 背景
+@bg_color_1: var(--color-bg-1); // 整体背景色
+@bg_color_2: var(--color-bg-2); // 一级容器背景
+@bg_color_3: var(--color-bg-3); // 二级容器背景
+@bg_color_4: var(--color-bg-4); // 三级容器背景
+@bg_color_5: var(--color-bg-5); // 下拉弹出框、Tooltip 背景颜色
+@bg_color_6: var(--color-bg-white); // 白色背景
+// 主色
+@blue_0: var(--primary-6);
+@blue_1: var(--primary-5);
+@blue_2: var(--primary-4);
+
+// 边框
+@black_1: var(--color-neutral-1);
+@black_2: var(--color-neutral-2);
+@black_3: var(--color-neutral-3);
+
+// 字体颜色
+@text_color_1: var(--color-text-1); // 强调/正文标题
+@text_color_2: var(--color-text-2); // 次强调/正文标题
+@text_color_3: var(--color-text-3); // 次要信息
+@text_color_4: var(--color-text-4); // 置灰信息

+ 539 - 0
src/assets/iconsvg/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

+ 2051 - 0
src/assets/iconsvg/demo_index.html

@@ -0,0 +1,2051 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>iconfont Demo</title>
+  <link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
+  <link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
+  <link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
+  <link rel="stylesheet" href="demo.css">
+  <link rel="stylesheet" href="iconfont.css">
+  <script src="iconfont.js"></script>
+  <!-- jQuery -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
+  <!-- 代码高亮 -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
+  <style>
+    .main .logo {
+      margin-top: 0;
+      height: auto;
+    }
+
+    .main .logo a {
+      display: flex;
+      align-items: center;
+    }
+
+    .main .logo .sub-title {
+      margin-left: 0.5em;
+      font-size: 22px;
+      color: #fff;
+      background: linear-gradient(-45deg, #3967FF, #B500FE);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+    }
+  </style>
+</head>
+<body>
+  <div class="main">
+    <h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
+      <img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
+      
+    </a></h1>
+    <div class="nav-tabs">
+      <ul id="tabs" class="dib-box">
+        <li class="dib active"><span>Unicode</span></li>
+        <li class="dib"><span>Font class</span></li>
+        <li class="dib"><span>Symbol</span></li>
+      </ul>
+      
+      <a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=3361042" target="_blank" class="nav-more">查看项目</a>
+      
+    </div>
+    <div class="tab-container">
+      <div class="content unicode" style="display: block;">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe657;</span>
+                <div class="name">办公1</div>
+                <div class="code-name">&amp;#xe657;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe658;</span>
+                <div class="name">单据流1</div>
+                <div class="code-name">&amp;#xe658;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe668;</span>
+                <div class="name">工具3</div>
+                <div class="code-name">&amp;#xe668;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe669;</span>
+                <div class="name">办公3</div>
+                <div class="code-name">&amp;#xe669;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66a;</span>
+                <div class="name">考勤管理3</div>
+                <div class="code-name">&amp;#xe66a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66b;</span>
+                <div class="name">数据3</div>
+                <div class="code-name">&amp;#xe66b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66c;</span>
+                <div class="name">首页3</div>
+                <div class="code-name">&amp;#xe66c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66d;</span>
+                <div class="name">供应商3</div>
+                <div class="code-name">&amp;#xe66d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66e;</span>
+                <div class="name">人事3</div>
+                <div class="code-name">&amp;#xe66e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66f;</span>
+                <div class="name">组织3</div>
+                <div class="code-name">&amp;#xe66f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe670;</span>
+                <div class="name">客户3</div>
+                <div class="code-name">&amp;#xe670;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe671;</span>
+                <div class="name">物料3</div>
+                <div class="code-name">&amp;#xe671;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe672;</span>
+                <div class="name">销售3</div>
+                <div class="code-name">&amp;#xe672;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe673;</span>
+                <div class="name">指标3</div>
+                <div class="code-name">&amp;#xe673;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe674;</span>
+                <div class="name">模版3</div>
+                <div class="code-name">&amp;#xe674;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe675;</span>
+                <div class="name">单据流3</div>
+                <div class="code-name">&amp;#xe675;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe676;</span>
+                <div class="name">项目3</div>
+                <div class="code-name">&amp;#xe676;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe678;</span>
+                <div class="name">业务3</div>
+                <div class="code-name">&amp;#xe678;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe679;</span>
+                <div class="name">模版</div>
+                <div class="code-name">&amp;#xe679;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe683;</span>
+                <div class="name">打印</div>
+                <div class="code-name">&amp;#xe683;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe684;</span>
+                <div class="name">出差</div>
+                <div class="code-name">&amp;#xe684;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe685;</span>
+                <div class="name">单据记录</div>
+                <div class="code-name">&amp;#xe685;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe686;</span>
+                <div class="name">岗位</div>
+                <div class="code-name">&amp;#xe686;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe687;</span>
+                <div class="name">发布</div>
+                <div class="code-name">&amp;#xe687;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe688;</span>
+                <div class="name">点赞</div>
+                <div class="code-name">&amp;#xe688;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68c;</span>
+                <div class="name">合作</div>
+                <div class="code-name">&amp;#xe68c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68d;</span>
+                <div class="name">工具</div>
+                <div class="code-name">&amp;#xe68d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe690;</span>
+                <div class="name">旗帜</div>
+                <div class="code-name">&amp;#xe690;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe693;</span>
+                <div class="name">人事记录</div>
+                <div class="code-name">&amp;#xe693;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe694;</span>
+                <div class="name">目标</div>
+                <div class="code-name">&amp;#xe694;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe695;</span>
+                <div class="name">时间</div>
+                <div class="code-name">&amp;#xe695;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69a;</span>
+                <div class="name">销售冠军</div>
+                <div class="code-name">&amp;#xe69a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69b;</span>
+                <div class="name">物联网</div>
+                <div class="code-name">&amp;#xe69b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69c;</span>
+                <div class="name">物料</div>
+                <div class="code-name">&amp;#xe69c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69d;</span>
+                <div class="name">add-account</div>
+                <div class="code-name">&amp;#xe69d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69e;</span>
+                <div class="name">office-supplies-fill</div>
+                <div class="code-name">&amp;#xe69e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69f;</span>
+                <div class="name">下午茶、员工福利</div>
+                <div class="code-name">&amp;#xe69f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a0;</span>
+                <div class="name">指标、计划、方向</div>
+                <div class="code-name">&amp;#xe6a0;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a1;</span>
+                <div class="name">指标</div>
+                <div class="code-name">&amp;#xe6a1;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a2;</span>
+                <div class="name">图钉</div>
+                <div class="code-name">&amp;#xe6a2;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a3;</span>
+                <div class="name">supplier-features-fill</div>
+                <div class="code-name">&amp;#xe6a3;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a4;</span>
+                <div class="name">模板</div>
+                <div class="code-name">&amp;#xe6a4;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a5;</span>
+                <div class="name">组织机构</div>
+                <div class="code-name">&amp;#xe6a5;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a6;</span>
+                <div class="name">资金</div>
+                <div class="code-name">&amp;#xe6a6;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a7;</span>
+                <div class="name">certified-supplier-fill</div>
+                <div class="code-name">&amp;#xe6a7;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68f;</span>
+                <div class="name">新闻</div>
+                <div class="code-name">&amp;#xe68f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e9;</span>
+                <div class="name">对象类型-客户2</div>
+                <div class="code-name">&amp;#xe6e9;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe70a;</span>
+                <div class="name">对象类型-指标群1</div>
+                <div class="code-name">&amp;#xe70a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe607;</span>
+                <div class="name">水滴</div>
+                <div class="code-name">&amp;#xe607;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe623;</span>
+                <div class="name">下拉框</div>
+                <div class="code-name">&amp;#xe623;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe707;</span>
+                <div class="name">排行榜</div>
+                <div class="code-name">&amp;#xe707;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7af;</span>
+                <div class="name">表格图表</div>
+                <div class="code-name">&amp;#xe7af;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe611;</span>
+                <div class="name">办公</div>
+                <div class="code-name">&amp;#xe611;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe619;</span>
+                <div class="name">组织</div>
+                <div class="code-name">&amp;#xe619;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61c;</span>
+                <div class="name">客户</div>
+                <div class="code-name">&amp;#xe61c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61d;</span>
+                <div class="name">物料</div>
+                <div class="code-name">&amp;#xe61d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61e;</span>
+                <div class="name">项目</div>
+                <div class="code-name">&amp;#xe61e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe621;</span>
+                <div class="name">指标</div>
+                <div class="code-name">&amp;#xe621;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe624;</span>
+                <div class="name">工具</div>
+                <div class="code-name">&amp;#xe624;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe626;</span>
+                <div class="name">考勤管理</div>
+                <div class="code-name">&amp;#xe626;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe65a;</span>
+                <div class="name">考勤管理1</div>
+                <div class="code-name">&amp;#xe65a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe65e;</span>
+                <div class="name">工具1</div>
+                <div class="code-name">&amp;#xe65e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe660;</span>
+                <div class="name">物料1</div>
+                <div class="code-name">&amp;#xe660;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe661;</span>
+                <div class="name">业务1</div>
+                <div class="code-name">&amp;#xe661;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe662;</span>
+                <div class="name">销售1</div>
+                <div class="code-name">&amp;#xe662;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe667;</span>
+                <div class="name">组织1</div>
+                <div class="code-name">&amp;#xe667;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68a;</span>
+                <div class="name">金牌销售</div>
+                <div class="code-name">&amp;#xe68a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68b;</span>
+                <div class="name">供应商</div>
+                <div class="code-name">&amp;#xe68b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe691;</span>
+                <div class="name">福利</div>
+                <div class="code-name">&amp;#xe691;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe697;</span>
+                <div class="name">品牌管理</div>
+                <div class="code-name">&amp;#xe697;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xf96a;</span>
+                <div class="name">时间</div>
+                <div class="code-name">&amp;#xf96a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe618;</span>
+                <div class="name">折线面积图 </div>
+                <div class="code-name">&amp;#xe618;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe60a;</span>
+                <div class="name">统计图</div>
+                <div class="code-name">&amp;#xe60a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe639;</span>
+                <div class="name">业绩面板</div>
+                <div class="code-name">&amp;#xe639;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe726;</span>
+                <div class="name">统计图</div>
+                <div class="code-name">&amp;#xe726;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xeba2;</span>
+                <div class="name">图钉</div>
+                <div class="code-name">&amp;#xeba2;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ca;</span>
+                <div class="name">图钉</div>
+                <div class="code-name">&amp;#xe6ca;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe605;</span>
+                <div class="name">palette</div>
+                <div class="code-name">&amp;#xe605;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe604;</span>
+                <div class="name">customer</div>
+                <div class="code-name">&amp;#xe604;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe603;</span>
+                <div class="name">dmp</div>
+                <div class="code-name">&amp;#xe603;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe602;</span>
+                <div class="name">role</div>
+                <div class="code-name">&amp;#xe602;</div>
+              </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="unicode-">Unicode 引用</h2>
+          <hr>
+
+          <p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
+          <ul>
+            <li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
+            <li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
+          </ul>
+          <blockquote>
+            <p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
+          </blockquote>
+          <p>Unicode 使用步骤如下:</p>
+          <h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
+<pre><code class="language-css"
+>@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1718608625611') format('woff2'),
+       url('iconfont.woff?t=1718608625611') format('woff'),
+       url('iconfont.ttf?t=1718608625611') format('truetype');
+}
+</code></pre>
+          <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
+<pre><code class="language-css"
+>.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
+<pre>
+<code class="language-html"
+>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
+</code></pre>
+          <blockquote>
+            <p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+          </blockquote>
+          </div>
+      </div>
+      <div class="content font-class">
+        <ul class="icon_lists dib-box">
+          
+          <li class="dib">
+            <span class="icon iconfont menu-bangong1"></span>
+            <div class="name">
+              办公1
+            </div>
+            <div class="code-name">.menu-bangong1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-danjuliu1"></span>
+            <div class="name">
+              单据流1
+            </div>
+            <div class="code-name">.menu-danjuliu1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-gongju3"></span>
+            <div class="name">
+              工具3
+            </div>
+            <div class="code-name">.menu-gongju3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-bangong3"></span>
+            <div class="name">
+              办公3
+            </div>
+            <div class="code-name">.menu-bangong3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-kaoqinguanli3"></span>
+            <div class="name">
+              考勤管理3
+            </div>
+            <div class="code-name">.menu-kaoqinguanli3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-shuju3"></span>
+            <div class="name">
+              数据3
+            </div>
+            <div class="code-name">.Dh-shuju3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-shouye3"></span>
+            <div class="name">
+              首页3
+            </div>
+            <div class="code-name">.Dh-shouye3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-gongyingshang3"></span>
+            <div class="name">
+              供应商3
+            </div>
+            <div class="code-name">.Dh-gongyingshang3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-renshi3"></span>
+            <div class="name">
+              人事3
+            </div>
+            <div class="code-name">.Dh-renshi3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zuzhi3"></span>
+            <div class="name">
+              组织3
+            </div>
+            <div class="code-name">.Dh-zuzhi3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-kehu3"></span>
+            <div class="name">
+              客户3
+            </div>
+            <div class="code-name">.Dh-kehu3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-wuliao3"></span>
+            <div class="name">
+              物料3
+            </div>
+            <div class="code-name">.Dh-wuliao3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-xiaoshou3"></span>
+            <div class="name">
+              销售3
+            </div>
+            <div class="code-name">.Dh-xiaoshou3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zhibiao3"></span>
+            <div class="name">
+              指标3
+            </div>
+            <div class="code-name">.Dh-zhibiao3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-moban3"></span>
+            <div class="name">
+              模版3
+            </div>
+            <div class="code-name">.Dh-moban3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-danjuliu3"></span>
+            <div class="name">
+              单据流3
+            </div>
+            <div class="code-name">.Dh-danjuliu3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-xiangmu3"></span>
+            <div class="name">
+              项目3
+            </div>
+            <div class="code-name">.Dh-xiangmu3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-yewu3"></span>
+            <div class="name">
+              业务3
+            </div>
+            <div class="code-name">.Dh-yewu3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-moban"></span>
+            <div class="name">
+              模版
+            </div>
+            <div class="code-name">.Dh-moban
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-dayin"></span>
+            <div class="name">
+              打印
+            </div>
+            <div class="code-name">.Dh-dayin
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-chucha"></span>
+            <div class="name">
+              出差
+            </div>
+            <div class="code-name">.Dh-chucha
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-danjujilu"></span>
+            <div class="name">
+              单据记录
+            </div>
+            <div class="code-name">.Dh-danjujilu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-gangwei1"></span>
+            <div class="name">
+              岗位
+            </div>
+            <div class="code-name">.Dh-gangwei1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-fabu"></span>
+            <div class="name">
+              发布
+            </div>
+            <div class="code-name">.Dh-fabu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-dianzan"></span>
+            <div class="name">
+              点赞
+            </div>
+            <div class="code-name">.Dh-dianzan
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-hezuo"></span>
+            <div class="name">
+              合作
+            </div>
+            <div class="code-name">.Dh-hezuo
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-gongju2"></span>
+            <div class="name">
+              工具
+            </div>
+            <div class="code-name">.Dh-gongju2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-qizhi"></span>
+            <div class="name">
+              旗帜
+            </div>
+            <div class="code-name">.Dh-qizhi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-renshijilu"></span>
+            <div class="name">
+              人事记录
+            </div>
+            <div class="code-name">.Dh-renshijilu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-mubiao"></span>
+            <div class="name">
+              目标
+            </div>
+            <div class="code-name">.Dh-mubiao
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-shijian"></span>
+            <div class="name">
+              时间
+            </div>
+            <div class="code-name">.Dh-shijian
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-xiaoshouguanjun"></span>
+            <div class="name">
+              销售冠军
+            </div>
+            <div class="code-name">.Dh-xiaoshouguanjun
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-wulianwang2"></span>
+            <div class="name">
+              物联网
+            </div>
+            <div class="code-name">.Dh-wulianwang2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-wuliao2"></span>
+            <div class="name">
+              物料
+            </div>
+            <div class="code-name">.Dh-wuliao2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-add"></span>
+            <div class="name">
+              add-account
+            </div>
+            <div class="code-name">.Dh-add
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-office-supplies"></span>
+            <div class="name">
+              office-supplies-fill
+            </div>
+            <div class="code-name">.Dh-office-supplies
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-xiawucha"></span>
+            <div class="name">
+              下午茶、员工福利
+            </div>
+            <div class="code-name">.Dh-xiawucha
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zhibiao2"></span>
+            <div class="name">
+              指标、计划、方向
+            </div>
+            <div class="code-name">.Dh-zhibiao2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zhibiao4"></span>
+            <div class="name">
+              指标
+            </div>
+            <div class="code-name">.Dh-zhibiao4
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-tuding"></span>
+            <div class="name">
+              图钉
+            </div>
+            <div class="code-name">.Dh-tuding
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-supplier-features"></span>
+            <div class="name">
+              supplier-features-fill
+            </div>
+            <div class="code-name">.Dh-supplier-features
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-moban2"></span>
+            <div class="name">
+              模板
+            </div>
+            <div class="code-name">.Dh-moban2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zuzhijigou"></span>
+            <div class="name">
+              组织机构
+            </div>
+            <div class="code-name">.Dh-zuzhijigou
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-zijin"></span>
+            <div class="name">
+              资金
+            </div>
+            <div class="code-name">.Dh-zijin
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-certified-supplier"></span>
+            <div class="name">
+              certified-supplier-fill
+            </div>
+            <div class="code-name">.Dh-certified-supplier
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont Dh-xinwen"></span>
+            <div class="name">
+              新闻
+            </div>
+            <div class="code-name">.Dh-xinwen
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont duixiangleixing-kehu2"></span>
+            <div class="name">
+              对象类型-客户2
+            </div>
+            <div class="code-name">.duixiangleixing-kehu2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont duixiangleixing-zhibiaoqun1"></span>
+            <div class="name">
+              对象类型-指标群1
+            </div>
+            <div class="code-name">.duixiangleixing-zhibiaoqun1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont shuidi"></span>
+            <div class="name">
+              水滴
+            </div>
+            <div class="code-name">.shuidi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont xialakuang"></span>
+            <div class="name">
+              下拉框
+            </div>
+            <div class="code-name">.xialakuang
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-ranking"></span>
+            <div class="name">
+              排行榜
+            </div>
+            <div class="code-name">.dmp-ranking
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-table"></span>
+            <div class="name">
+              表格图表
+            </div>
+            <div class="code-name">.dmp-table
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-bangong"></span>
+            <div class="name">
+              办公
+            </div>
+            <div class="code-name">.menu-bangong
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-zuzhi"></span>
+            <div class="name">
+              组织
+            </div>
+            <div class="code-name">.menu-zuzhi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-kehu"></span>
+            <div class="name">
+              客户
+            </div>
+            <div class="code-name">.menu-kehu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-wuliao"></span>
+            <div class="name">
+              物料
+            </div>
+            <div class="code-name">.menu-wuliao
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-xiangmu"></span>
+            <div class="name">
+              项目
+            </div>
+            <div class="code-name">.menu-xiangmu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-zhibiao"></span>
+            <div class="name">
+              指标
+            </div>
+            <div class="code-name">.menu-zhibiao
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-gongju"></span>
+            <div class="name">
+              工具
+            </div>
+            <div class="code-name">.menu-gongju
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-kaoqinguanli"></span>
+            <div class="name">
+              考勤管理
+            </div>
+            <div class="code-name">.menu-kaoqinguanli
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-kaoqinguanli1"></span>
+            <div class="name">
+              考勤管理1
+            </div>
+            <div class="code-name">.menu-kaoqinguanli1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-gongju1"></span>
+            <div class="name">
+              工具1
+            </div>
+            <div class="code-name">.menu-gongju1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-wuliao1"></span>
+            <div class="name">
+              物料1
+            </div>
+            <div class="code-name">.menu-wuliao1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-yewu1"></span>
+            <div class="name">
+              业务1
+            </div>
+            <div class="code-name">.menu-yewu1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-xiaoshou1"></span>
+            <div class="name">
+              销售1
+            </div>
+            <div class="code-name">.menu-xiaoshou1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-zuzhi1"></span>
+            <div class="name">
+              组织1
+            </div>
+            <div class="code-name">.menu-zuzhi1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-jinpaixiaoshou"></span>
+            <div class="name">
+              金牌销售
+            </div>
+            <div class="code-name">.menu-jinpaixiaoshou
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-gongyingshang2"></span>
+            <div class="name">
+              供应商
+            </div>
+            <div class="code-name">.menu-gongyingshang2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-fuli"></span>
+            <div class="name">
+              福利
+            </div>
+            <div class="code-name">.menu-fuli
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-pinpaiguanli"></span>
+            <div class="name">
+              品牌管理
+            </div>
+            <div class="code-name">.menu-pinpaiguanli
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-time"></span>
+            <div class="name">
+              时间
+            </div>
+            <div class="code-name">.dmp-time
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-lin"></span>
+            <div class="name">
+              折线面积图 
+            </div>
+            <div class="code-name">.dmp-lin
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-bar"></span>
+            <div class="name">
+              统计图
+            </div>
+            <div class="code-name">.dmp-bar
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-card"></span>
+            <div class="name">
+              业绩面板
+            </div>
+            <div class="code-name">.dmp-card
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont dmp-pie"></span>
+            <div class="name">
+              统计图
+            </div>
+            <div class="code-name">.dmp-pie
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont pushpin-fill"></span>
+            <div class="name">
+              图钉
+            </div>
+            <div class="code-name">.pushpin-fill
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont tuding"></span>
+            <div class="name">
+              图钉
+            </div>
+            <div class="code-name">.tuding
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-palette"></span>
+            <div class="name">
+              palette
+            </div>
+            <div class="code-name">.menu-palette
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-customer"></span>
+            <div class="name">
+              customer
+            </div>
+            <div class="code-name">.menu-customer
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont menu-dmp"></span>
+            <div class="name">
+              dmp
+            </div>
+            <div class="code-name">.menu-dmp
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont role"></span>
+            <div class="name">
+              role
+            </div>
+            <div class="code-name">.role
+            </div>
+          </li>
+          
+        </ul>
+        <div class="article markdown">
+        <h2 id="font-class-">font-class 引用</h2>
+        <hr>
+
+        <p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
+        <p>与 Unicode 使用方式相比,具有如下特点:</p>
+        <ul>
+          <li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
+          <li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
+        </ul>
+        <p>使用步骤如下:</p>
+        <h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
+<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
+</code></pre>
+        <h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;span class="iconfont xxx"&gt;&lt;/span&gt;
+</code></pre>
+        <blockquote>
+          <p>"
+            iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+        </blockquote>
+      </div>
+      </div>
+      <div class="content symbol">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-bangong1"></use>
+                </svg>
+                <div class="name">办公1</div>
+                <div class="code-name">#menu-bangong1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-danjuliu1"></use>
+                </svg>
+                <div class="name">单据流1</div>
+                <div class="code-name">#menu-danjuliu1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-gongju3"></use>
+                </svg>
+                <div class="name">工具3</div>
+                <div class="code-name">#menu-gongju3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-bangong3"></use>
+                </svg>
+                <div class="name">办公3</div>
+                <div class="code-name">#menu-bangong3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-kaoqinguanli3"></use>
+                </svg>
+                <div class="name">考勤管理3</div>
+                <div class="code-name">#menu-kaoqinguanli3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-shuju3"></use>
+                </svg>
+                <div class="name">数据3</div>
+                <div class="code-name">#Dh-shuju3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-shouye3"></use>
+                </svg>
+                <div class="name">首页3</div>
+                <div class="code-name">#Dh-shouye3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-gongyingshang3"></use>
+                </svg>
+                <div class="name">供应商3</div>
+                <div class="code-name">#Dh-gongyingshang3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-renshi3"></use>
+                </svg>
+                <div class="name">人事3</div>
+                <div class="code-name">#Dh-renshi3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zuzhi3"></use>
+                </svg>
+                <div class="name">组织3</div>
+                <div class="code-name">#Dh-zuzhi3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-kehu3"></use>
+                </svg>
+                <div class="name">客户3</div>
+                <div class="code-name">#Dh-kehu3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-wuliao3"></use>
+                </svg>
+                <div class="name">物料3</div>
+                <div class="code-name">#Dh-wuliao3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-xiaoshou3"></use>
+                </svg>
+                <div class="name">销售3</div>
+                <div class="code-name">#Dh-xiaoshou3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zhibiao3"></use>
+                </svg>
+                <div class="name">指标3</div>
+                <div class="code-name">#Dh-zhibiao3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-moban3"></use>
+                </svg>
+                <div class="name">模版3</div>
+                <div class="code-name">#Dh-moban3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-danjuliu3"></use>
+                </svg>
+                <div class="name">单据流3</div>
+                <div class="code-name">#Dh-danjuliu3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-xiangmu3"></use>
+                </svg>
+                <div class="name">项目3</div>
+                <div class="code-name">#Dh-xiangmu3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-yewu3"></use>
+                </svg>
+                <div class="name">业务3</div>
+                <div class="code-name">#Dh-yewu3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-moban"></use>
+                </svg>
+                <div class="name">模版</div>
+                <div class="code-name">#Dh-moban</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-dayin"></use>
+                </svg>
+                <div class="name">打印</div>
+                <div class="code-name">#Dh-dayin</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-chucha"></use>
+                </svg>
+                <div class="name">出差</div>
+                <div class="code-name">#Dh-chucha</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-danjujilu"></use>
+                </svg>
+                <div class="name">单据记录</div>
+                <div class="code-name">#Dh-danjujilu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-gangwei1"></use>
+                </svg>
+                <div class="name">岗位</div>
+                <div class="code-name">#Dh-gangwei1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-fabu"></use>
+                </svg>
+                <div class="name">发布</div>
+                <div class="code-name">#Dh-fabu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-dianzan"></use>
+                </svg>
+                <div class="name">点赞</div>
+                <div class="code-name">#Dh-dianzan</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-hezuo"></use>
+                </svg>
+                <div class="name">合作</div>
+                <div class="code-name">#Dh-hezuo</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-gongju2"></use>
+                </svg>
+                <div class="name">工具</div>
+                <div class="code-name">#Dh-gongju2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-qizhi"></use>
+                </svg>
+                <div class="name">旗帜</div>
+                <div class="code-name">#Dh-qizhi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-renshijilu"></use>
+                </svg>
+                <div class="name">人事记录</div>
+                <div class="code-name">#Dh-renshijilu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-mubiao"></use>
+                </svg>
+                <div class="name">目标</div>
+                <div class="code-name">#Dh-mubiao</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-shijian"></use>
+                </svg>
+                <div class="name">时间</div>
+                <div class="code-name">#Dh-shijian</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-xiaoshouguanjun"></use>
+                </svg>
+                <div class="name">销售冠军</div>
+                <div class="code-name">#Dh-xiaoshouguanjun</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-wulianwang2"></use>
+                </svg>
+                <div class="name">物联网</div>
+                <div class="code-name">#Dh-wulianwang2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-wuliao2"></use>
+                </svg>
+                <div class="name">物料</div>
+                <div class="code-name">#Dh-wuliao2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-add"></use>
+                </svg>
+                <div class="name">add-account</div>
+                <div class="code-name">#Dh-add</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-office-supplies"></use>
+                </svg>
+                <div class="name">office-supplies-fill</div>
+                <div class="code-name">#Dh-office-supplies</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-xiawucha"></use>
+                </svg>
+                <div class="name">下午茶、员工福利</div>
+                <div class="code-name">#Dh-xiawucha</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zhibiao2"></use>
+                </svg>
+                <div class="name">指标、计划、方向</div>
+                <div class="code-name">#Dh-zhibiao2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zhibiao4"></use>
+                </svg>
+                <div class="name">指标</div>
+                <div class="code-name">#Dh-zhibiao4</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-tuding"></use>
+                </svg>
+                <div class="name">图钉</div>
+                <div class="code-name">#Dh-tuding</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-supplier-features"></use>
+                </svg>
+                <div class="name">supplier-features-fill</div>
+                <div class="code-name">#Dh-supplier-features</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-moban2"></use>
+                </svg>
+                <div class="name">模板</div>
+                <div class="code-name">#Dh-moban2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zuzhijigou"></use>
+                </svg>
+                <div class="name">组织机构</div>
+                <div class="code-name">#Dh-zuzhijigou</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-zijin"></use>
+                </svg>
+                <div class="name">资金</div>
+                <div class="code-name">#Dh-zijin</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-certified-supplier"></use>
+                </svg>
+                <div class="name">certified-supplier-fill</div>
+                <div class="code-name">#Dh-certified-supplier</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#Dh-xinwen"></use>
+                </svg>
+                <div class="name">新闻</div>
+                <div class="code-name">#Dh-xinwen</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#duixiangleixing-kehu2"></use>
+                </svg>
+                <div class="name">对象类型-客户2</div>
+                <div class="code-name">#duixiangleixing-kehu2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#duixiangleixing-zhibiaoqun1"></use>
+                </svg>
+                <div class="name">对象类型-指标群1</div>
+                <div class="code-name">#duixiangleixing-zhibiaoqun1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#shuidi"></use>
+                </svg>
+                <div class="name">水滴</div>
+                <div class="code-name">#shuidi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#xialakuang"></use>
+                </svg>
+                <div class="name">下拉框</div>
+                <div class="code-name">#xialakuang</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-ranking"></use>
+                </svg>
+                <div class="name">排行榜</div>
+                <div class="code-name">#dmp-ranking</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-table"></use>
+                </svg>
+                <div class="name">表格图表</div>
+                <div class="code-name">#dmp-table</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-bangong"></use>
+                </svg>
+                <div class="name">办公</div>
+                <div class="code-name">#menu-bangong</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-zuzhi"></use>
+                </svg>
+                <div class="name">组织</div>
+                <div class="code-name">#menu-zuzhi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-kehu"></use>
+                </svg>
+                <div class="name">客户</div>
+                <div class="code-name">#menu-kehu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-wuliao"></use>
+                </svg>
+                <div class="name">物料</div>
+                <div class="code-name">#menu-wuliao</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-xiangmu"></use>
+                </svg>
+                <div class="name">项目</div>
+                <div class="code-name">#menu-xiangmu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-zhibiao"></use>
+                </svg>
+                <div class="name">指标</div>
+                <div class="code-name">#menu-zhibiao</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-gongju"></use>
+                </svg>
+                <div class="name">工具</div>
+                <div class="code-name">#menu-gongju</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-kaoqinguanli"></use>
+                </svg>
+                <div class="name">考勤管理</div>
+                <div class="code-name">#menu-kaoqinguanli</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-kaoqinguanli1"></use>
+                </svg>
+                <div class="name">考勤管理1</div>
+                <div class="code-name">#menu-kaoqinguanli1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-gongju1"></use>
+                </svg>
+                <div class="name">工具1</div>
+                <div class="code-name">#menu-gongju1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-wuliao1"></use>
+                </svg>
+                <div class="name">物料1</div>
+                <div class="code-name">#menu-wuliao1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-yewu1"></use>
+                </svg>
+                <div class="name">业务1</div>
+                <div class="code-name">#menu-yewu1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-xiaoshou1"></use>
+                </svg>
+                <div class="name">销售1</div>
+                <div class="code-name">#menu-xiaoshou1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-zuzhi1"></use>
+                </svg>
+                <div class="name">组织1</div>
+                <div class="code-name">#menu-zuzhi1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-jinpaixiaoshou"></use>
+                </svg>
+                <div class="name">金牌销售</div>
+                <div class="code-name">#menu-jinpaixiaoshou</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-gongyingshang2"></use>
+                </svg>
+                <div class="name">供应商</div>
+                <div class="code-name">#menu-gongyingshang2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-fuli"></use>
+                </svg>
+                <div class="name">福利</div>
+                <div class="code-name">#menu-fuli</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-pinpaiguanli"></use>
+                </svg>
+                <div class="name">品牌管理</div>
+                <div class="code-name">#menu-pinpaiguanli</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-time"></use>
+                </svg>
+                <div class="name">时间</div>
+                <div class="code-name">#dmp-time</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-lin"></use>
+                </svg>
+                <div class="name">折线面积图 </div>
+                <div class="code-name">#dmp-lin</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-bar"></use>
+                </svg>
+                <div class="name">统计图</div>
+                <div class="code-name">#dmp-bar</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-card"></use>
+                </svg>
+                <div class="name">业绩面板</div>
+                <div class="code-name">#dmp-card</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#dmp-pie"></use>
+                </svg>
+                <div class="name">统计图</div>
+                <div class="code-name">#dmp-pie</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#pushpin-fill"></use>
+                </svg>
+                <div class="name">图钉</div>
+                <div class="code-name">#pushpin-fill</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#tuding"></use>
+                </svg>
+                <div class="name">图钉</div>
+                <div class="code-name">#tuding</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-palette"></use>
+                </svg>
+                <div class="name">palette</div>
+                <div class="code-name">#menu-palette</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-customer"></use>
+                </svg>
+                <div class="name">customer</div>
+                <div class="code-name">#menu-customer</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#menu-dmp"></use>
+                </svg>
+                <div class="name">dmp</div>
+                <div class="code-name">#menu-dmp</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#role"></use>
+                </svg>
+                <div class="name">role</div>
+                <div class="code-name">#role</div>
+            </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="symbol-">Symbol 引用</h2>
+          <hr>
+
+          <p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
+            这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
+          <ul>
+            <li>支持多色图标了,不再受单色限制。</li>
+            <li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
+            <li>兼容性较差,支持 IE9+,及现代浏览器。</li>
+            <li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
+          </ul>
+          <p>使用步骤如下:</p>
+          <h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
+<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
+</code></pre>
+          <h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
+<pre><code class="language-html">&lt;style&gt;
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+&lt;/style&gt;
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
+  &lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
+&lt;/svg&gt;
+</code></pre>
+          </div>
+      </div>
+
+    </div>
+  </div>
+  <script>
+  $(document).ready(function () {
+      $('.tab-container .content:first').show()
+
+      $('#tabs li').click(function (e) {
+        var tabContent = $('.tab-container .content')
+        var index = $(this).index()
+
+        if ($(this).hasClass('active')) {
+          return
+        } else {
+          $('#tabs li').removeClass('active')
+          $(this).addClass('active')
+
+          tabContent.hide().eq(index).fadeIn()
+        }
+      })
+    })
+  </script>
+</body>
+</html>

+ 339 - 0
src/assets/iconsvg/iconfont.css

@@ -0,0 +1,339 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 3361042 */
+  src: url('iconfont.woff2?t=1718608625611') format('woff2'),
+       url('iconfont.woff?t=1718608625611') format('woff'),
+       url('iconfont.ttf?t=1718608625611') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.menu-bangong1:before {
+  content: "\e657";
+}
+
+.menu-danjuliu1:before {
+  content: "\e658";
+}
+
+.menu-gongju3:before {
+  content: "\e668";
+}
+
+.menu-bangong3:before {
+  content: "\e669";
+}
+
+.menu-kaoqinguanli3:before {
+  content: "\e66a";
+}
+
+.Dh-shuju3:before {
+  content: "\e66b";
+}
+
+.Dh-shouye3:before {
+  content: "\e66c";
+}
+
+.Dh-gongyingshang3:before {
+  content: "\e66d";
+}
+
+.Dh-renshi3:before {
+  content: "\e66e";
+}
+
+.Dh-zuzhi3:before {
+  content: "\e66f";
+}
+
+.Dh-kehu3:before {
+  content: "\e670";
+}
+
+.Dh-wuliao3:before {
+  content: "\e671";
+}
+
+.Dh-xiaoshou3:before {
+  content: "\e672";
+}
+
+.Dh-zhibiao3:before {
+  content: "\e673";
+}
+
+.Dh-moban3:before {
+  content: "\e674";
+}
+
+.Dh-danjuliu3:before {
+  content: "\e675";
+}
+
+.Dh-xiangmu3:before {
+  content: "\e676";
+}
+
+.Dh-yewu3:before {
+  content: "\e678";
+}
+
+.Dh-moban:before {
+  content: "\e679";
+}
+
+.Dh-dayin:before {
+  content: "\e683";
+}
+
+.Dh-chucha:before {
+  content: "\e684";
+}
+
+.Dh-danjujilu:before {
+  content: "\e685";
+}
+
+.Dh-gangwei1:before {
+  content: "\e686";
+}
+
+.Dh-fabu:before {
+  content: "\e687";
+}
+
+.Dh-dianzan:before {
+  content: "\e688";
+}
+
+.Dh-hezuo:before {
+  content: "\e68c";
+}
+
+.Dh-gongju2:before {
+  content: "\e68d";
+}
+
+.Dh-qizhi:before {
+  content: "\e690";
+}
+
+.Dh-renshijilu:before {
+  content: "\e693";
+}
+
+.Dh-mubiao:before {
+  content: "\e694";
+}
+
+.Dh-shijian:before {
+  content: "\e695";
+}
+
+.Dh-xiaoshouguanjun:before {
+  content: "\e69a";
+}
+
+.Dh-wulianwang2:before {
+  content: "\e69b";
+}
+
+.Dh-wuliao2:before {
+  content: "\e69c";
+}
+
+.Dh-add:before {
+  content: "\e69d";
+}
+
+.Dh-office-supplies:before {
+  content: "\e69e";
+}
+
+.Dh-xiawucha:before {
+  content: "\e69f";
+}
+
+.Dh-zhibiao2:before {
+  content: "\e6a0";
+}
+
+.Dh-zhibiao4:before {
+  content: "\e6a1";
+}
+
+.Dh-tuding:before {
+  content: "\e6a2";
+}
+
+.Dh-supplier-features:before {
+  content: "\e6a3";
+}
+
+.Dh-moban2:before {
+  content: "\e6a4";
+}
+
+.Dh-zuzhijigou:before {
+  content: "\e6a5";
+}
+
+.Dh-zijin:before {
+  content: "\e6a6";
+}
+
+.Dh-certified-supplier:before {
+  content: "\e6a7";
+}
+
+.Dh-xinwen:before {
+  content: "\e68f";
+}
+
+.duixiangleixing-kehu2:before {
+  content: "\e6e9";
+}
+
+.duixiangleixing-zhibiaoqun1:before {
+  content: "\e70a";
+}
+
+.shuidi:before {
+  content: "\e607";
+}
+
+.xialakuang:before {
+  content: "\e623";
+}
+
+.dmp-ranking:before {
+  content: "\e707";
+}
+
+.dmp-table:before {
+  content: "\e7af";
+}
+
+.menu-bangong:before {
+  content: "\e611";
+}
+
+.menu-zuzhi:before {
+  content: "\e619";
+}
+
+.menu-kehu:before {
+  content: "\e61c";
+}
+
+.menu-wuliao:before {
+  content: "\e61d";
+}
+
+.menu-xiangmu:before {
+  content: "\e61e";
+}
+
+.menu-zhibiao:before {
+  content: "\e621";
+}
+
+.menu-gongju:before {
+  content: "\e624";
+}
+
+.menu-kaoqinguanli:before {
+  content: "\e626";
+}
+
+.menu-kaoqinguanli1:before {
+  content: "\e65a";
+}
+
+.menu-gongju1:before {
+  content: "\e65e";
+}
+
+.menu-wuliao1:before {
+  content: "\e660";
+}
+
+.menu-yewu1:before {
+  content: "\e661";
+}
+
+.menu-xiaoshou1:before {
+  content: "\e662";
+}
+
+.menu-zuzhi1:before {
+  content: "\e667";
+}
+
+.menu-jinpaixiaoshou:before {
+  content: "\e68a";
+}
+
+.menu-gongyingshang2:before {
+  content: "\e68b";
+}
+
+.menu-fuli:before {
+  content: "\e691";
+}
+
+.menu-pinpaiguanli:before {
+  content: "\e697";
+}
+
+.dmp-time:before {
+  content: "\f96a";
+}
+
+.dmp-lin:before {
+  content: "\e618";
+}
+
+.dmp-bar:before {
+  content: "\e60a";
+}
+
+.dmp-card:before {
+  content: "\e639";
+}
+
+.dmp-pie:before {
+  content: "\e726";
+}
+
+.pushpin-fill:before {
+  content: "\eba2";
+}
+
+.tuding:before {
+  content: "\e6ca";
+}
+
+.menu-palette:before {
+  content: "\e605";
+}
+
+.menu-customer:before {
+  content: "\e604";
+}
+
+.menu-dmp:before {
+  content: "\e603";
+}
+
+.role:before {
+  content: "\e602";
+}
+

File diff suppressed because it is too large
+ 0 - 0
src/assets/iconsvg/iconfont.js


+ 576 - 0
src/assets/iconsvg/iconfont.json

@@ -0,0 +1,576 @@
+{
+  "id": "3361042",
+  "name": "DMP",
+  "font_family": "iconfont",
+  "css_prefix_text": "",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "22485477",
+      "name": "办公1",
+      "font_class": "menu-bangong1",
+      "unicode": "e657",
+      "unicode_decimal": 58967
+    },
+    {
+      "icon_id": "22485478",
+      "name": "单据流1",
+      "font_class": "menu-danjuliu1",
+      "unicode": "e658",
+      "unicode_decimal": 58968
+    },
+    {
+      "icon_id": "22485623",
+      "name": "工具3",
+      "font_class": "menu-gongju3",
+      "unicode": "e668",
+      "unicode_decimal": 58984
+    },
+    {
+      "icon_id": "22485624",
+      "name": "办公3",
+      "font_class": "menu-bangong3",
+      "unicode": "e669",
+      "unicode_decimal": 58985
+    },
+    {
+      "icon_id": "22485625",
+      "name": "考勤管理3",
+      "font_class": "menu-kaoqinguanli3",
+      "unicode": "e66a",
+      "unicode_decimal": 58986
+    },
+    {
+      "icon_id": "22485626",
+      "name": "数据3",
+      "font_class": "Dh-shuju3",
+      "unicode": "e66b",
+      "unicode_decimal": 58987
+    },
+    {
+      "icon_id": "22485627",
+      "name": "首页3",
+      "font_class": "Dh-shouye3",
+      "unicode": "e66c",
+      "unicode_decimal": 58988
+    },
+    {
+      "icon_id": "22485628",
+      "name": "供应商3",
+      "font_class": "Dh-gongyingshang3",
+      "unicode": "e66d",
+      "unicode_decimal": 58989
+    },
+    {
+      "icon_id": "22485629",
+      "name": "人事3",
+      "font_class": "Dh-renshi3",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "22485630",
+      "name": "组织3",
+      "font_class": "Dh-zuzhi3",
+      "unicode": "e66f",
+      "unicode_decimal": 58991
+    },
+    {
+      "icon_id": "22485631",
+      "name": "客户3",
+      "font_class": "Dh-kehu3",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    },
+    {
+      "icon_id": "22485632",
+      "name": "物料3",
+      "font_class": "Dh-wuliao3",
+      "unicode": "e671",
+      "unicode_decimal": 58993
+    },
+    {
+      "icon_id": "22485633",
+      "name": "销售3",
+      "font_class": "Dh-xiaoshou3",
+      "unicode": "e672",
+      "unicode_decimal": 58994
+    },
+    {
+      "icon_id": "22485634",
+      "name": "指标3",
+      "font_class": "Dh-zhibiao3",
+      "unicode": "e673",
+      "unicode_decimal": 58995
+    },
+    {
+      "icon_id": "22485635",
+      "name": "模版3",
+      "font_class": "Dh-moban3",
+      "unicode": "e674",
+      "unicode_decimal": 58996
+    },
+    {
+      "icon_id": "22485636",
+      "name": "单据流3",
+      "font_class": "Dh-danjuliu3",
+      "unicode": "e675",
+      "unicode_decimal": 58997
+    },
+    {
+      "icon_id": "22485637",
+      "name": "项目3",
+      "font_class": "Dh-xiangmu3",
+      "unicode": "e676",
+      "unicode_decimal": 58998
+    },
+    {
+      "icon_id": "22485640",
+      "name": "业务3",
+      "font_class": "Dh-yewu3",
+      "unicode": "e678",
+      "unicode_decimal": 59000
+    },
+    {
+      "icon_id": "22890968",
+      "name": "模版",
+      "font_class": "Dh-moban",
+      "unicode": "e679",
+      "unicode_decimal": 59001
+    },
+    {
+      "icon_id": "23343182",
+      "name": "打印",
+      "font_class": "Dh-dayin",
+      "unicode": "e683",
+      "unicode_decimal": 59011
+    },
+    {
+      "icon_id": "23343183",
+      "name": "出差",
+      "font_class": "Dh-chucha",
+      "unicode": "e684",
+      "unicode_decimal": 59012
+    },
+    {
+      "icon_id": "23343184",
+      "name": "单据记录",
+      "font_class": "Dh-danjujilu",
+      "unicode": "e685",
+      "unicode_decimal": 59013
+    },
+    {
+      "icon_id": "23343185",
+      "name": "岗位",
+      "font_class": "Dh-gangwei1",
+      "unicode": "e686",
+      "unicode_decimal": 59014
+    },
+    {
+      "icon_id": "23343186",
+      "name": "发布",
+      "font_class": "Dh-fabu",
+      "unicode": "e687",
+      "unicode_decimal": 59015
+    },
+    {
+      "icon_id": "23343187",
+      "name": "点赞",
+      "font_class": "Dh-dianzan",
+      "unicode": "e688",
+      "unicode_decimal": 59016
+    },
+    {
+      "icon_id": "23343191",
+      "name": "合作",
+      "font_class": "Dh-hezuo",
+      "unicode": "e68c",
+      "unicode_decimal": 59020
+    },
+    {
+      "icon_id": "23343192",
+      "name": "工具",
+      "font_class": "Dh-gongju2",
+      "unicode": "e68d",
+      "unicode_decimal": 59021
+    },
+    {
+      "icon_id": "23343195",
+      "name": "旗帜",
+      "font_class": "Dh-qizhi",
+      "unicode": "e690",
+      "unicode_decimal": 59024
+    },
+    {
+      "icon_id": "23343198",
+      "name": "人事记录",
+      "font_class": "Dh-renshijilu",
+      "unicode": "e693",
+      "unicode_decimal": 59027
+    },
+    {
+      "icon_id": "23343199",
+      "name": "目标",
+      "font_class": "Dh-mubiao",
+      "unicode": "e694",
+      "unicode_decimal": 59028
+    },
+    {
+      "icon_id": "23343200",
+      "name": "时间",
+      "font_class": "Dh-shijian",
+      "unicode": "e695",
+      "unicode_decimal": 59029
+    },
+    {
+      "icon_id": "23343205",
+      "name": "销售冠军",
+      "font_class": "Dh-xiaoshouguanjun",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "23343206",
+      "name": "物联网",
+      "font_class": "Dh-wulianwang2",
+      "unicode": "e69b",
+      "unicode_decimal": 59035
+    },
+    {
+      "icon_id": "23343207",
+      "name": "物料",
+      "font_class": "Dh-wuliao2",
+      "unicode": "e69c",
+      "unicode_decimal": 59036
+    },
+    {
+      "icon_id": "23343208",
+      "name": "add-account",
+      "font_class": "Dh-add",
+      "unicode": "e69d",
+      "unicode_decimal": 59037
+    },
+    {
+      "icon_id": "23343209",
+      "name": "office-supplies-fill",
+      "font_class": "Dh-office-supplies",
+      "unicode": "e69e",
+      "unicode_decimal": 59038
+    },
+    {
+      "icon_id": "23343210",
+      "name": "下午茶、员工福利",
+      "font_class": "Dh-xiawucha",
+      "unicode": "e69f",
+      "unicode_decimal": 59039
+    },
+    {
+      "icon_id": "23343211",
+      "name": "指标、计划、方向",
+      "font_class": "Dh-zhibiao2",
+      "unicode": "e6a0",
+      "unicode_decimal": 59040
+    },
+    {
+      "icon_id": "23343212",
+      "name": "指标",
+      "font_class": "Dh-zhibiao4",
+      "unicode": "e6a1",
+      "unicode_decimal": 59041
+    },
+    {
+      "icon_id": "23343213",
+      "name": "图钉",
+      "font_class": "Dh-tuding",
+      "unicode": "e6a2",
+      "unicode_decimal": 59042
+    },
+    {
+      "icon_id": "23343214",
+      "name": "supplier-features-fill",
+      "font_class": "Dh-supplier-features",
+      "unicode": "e6a3",
+      "unicode_decimal": 59043
+    },
+    {
+      "icon_id": "23343215",
+      "name": "模板",
+      "font_class": "Dh-moban2",
+      "unicode": "e6a4",
+      "unicode_decimal": 59044
+    },
+    {
+      "icon_id": "23343216",
+      "name": "组织机构",
+      "font_class": "Dh-zuzhijigou",
+      "unicode": "e6a5",
+      "unicode_decimal": 59045
+    },
+    {
+      "icon_id": "23343217",
+      "name": "资金",
+      "font_class": "Dh-zijin",
+      "unicode": "e6a6",
+      "unicode_decimal": 59046
+    },
+    {
+      "icon_id": "23343218",
+      "name": "certified-supplier-fill",
+      "font_class": "Dh-certified-supplier",
+      "unicode": "e6a7",
+      "unicode_decimal": 59047
+    },
+    {
+      "icon_id": "23581915",
+      "name": "新闻",
+      "font_class": "Dh-xinwen",
+      "unicode": "e68f",
+      "unicode_decimal": 59023
+    },
+    {
+      "icon_id": "24323199",
+      "name": "对象类型-客户2",
+      "font_class": "duixiangleixing-kehu2",
+      "unicode": "e6e9",
+      "unicode_decimal": 59113
+    },
+    {
+      "icon_id": "24323233",
+      "name": "对象类型-指标群1",
+      "font_class": "duixiangleixing-zhibiaoqun1",
+      "unicode": "e70a",
+      "unicode_decimal": 59146
+    },
+    {
+      "icon_id": "3718747",
+      "name": "水滴",
+      "font_class": "shuidi",
+      "unicode": "e607",
+      "unicode_decimal": 58887
+    },
+    {
+      "icon_id": "15989504",
+      "name": "下拉框",
+      "font_class": "xialakuang",
+      "unicode": "e623",
+      "unicode_decimal": 58915
+    },
+    {
+      "icon_id": "29602546",
+      "name": "排行榜",
+      "font_class": "dmp-ranking",
+      "unicode": "e707",
+      "unicode_decimal": 59143
+    },
+    {
+      "icon_id": "23043840",
+      "name": "表格图表",
+      "font_class": "dmp-table",
+      "unicode": "e7af",
+      "unicode_decimal": 59311
+    },
+    {
+      "icon_id": "21208318",
+      "name": "办公",
+      "font_class": "menu-bangong",
+      "unicode": "e611",
+      "unicode_decimal": 58897
+    },
+    {
+      "icon_id": "21209884",
+      "name": "组织",
+      "font_class": "menu-zuzhi",
+      "unicode": "e619",
+      "unicode_decimal": 58905
+    },
+    {
+      "icon_id": "21466771",
+      "name": "客户",
+      "font_class": "menu-kehu",
+      "unicode": "e61c",
+      "unicode_decimal": 58908
+    },
+    {
+      "icon_id": "21466795",
+      "name": "物料",
+      "font_class": "menu-wuliao",
+      "unicode": "e61d",
+      "unicode_decimal": 58909
+    },
+    {
+      "icon_id": "21469438",
+      "name": "项目",
+      "font_class": "menu-xiangmu",
+      "unicode": "e61e",
+      "unicode_decimal": 58910
+    },
+    {
+      "icon_id": "21506935",
+      "name": "指标",
+      "font_class": "menu-zhibiao",
+      "unicode": "e621",
+      "unicode_decimal": 58913
+    },
+    {
+      "icon_id": "21553038",
+      "name": "工具",
+      "font_class": "menu-gongju",
+      "unicode": "e624",
+      "unicode_decimal": 58916
+    },
+    {
+      "icon_id": "21738654",
+      "name": "考勤管理",
+      "font_class": "menu-kaoqinguanli",
+      "unicode": "e626",
+      "unicode_decimal": 58918
+    },
+    {
+      "icon_id": "22485480",
+      "name": "考勤管理1",
+      "font_class": "menu-kaoqinguanli1",
+      "unicode": "e65a",
+      "unicode_decimal": 58970
+    },
+    {
+      "icon_id": "22485484",
+      "name": "工具1",
+      "font_class": "menu-gongju1",
+      "unicode": "e65e",
+      "unicode_decimal": 58974
+    },
+    {
+      "icon_id": "22485486",
+      "name": "物料1",
+      "font_class": "menu-wuliao1",
+      "unicode": "e660",
+      "unicode_decimal": 58976
+    },
+    {
+      "icon_id": "22485487",
+      "name": "业务1",
+      "font_class": "menu-yewu1",
+      "unicode": "e661",
+      "unicode_decimal": 58977
+    },
+    {
+      "icon_id": "22485488",
+      "name": "销售1",
+      "font_class": "menu-xiaoshou1",
+      "unicode": "e662",
+      "unicode_decimal": 58978
+    },
+    {
+      "icon_id": "22485493",
+      "name": "组织1",
+      "font_class": "menu-zuzhi1",
+      "unicode": "e667",
+      "unicode_decimal": 58983
+    },
+    {
+      "icon_id": "23343189",
+      "name": "金牌销售",
+      "font_class": "menu-jinpaixiaoshou",
+      "unicode": "e68a",
+      "unicode_decimal": 59018
+    },
+    {
+      "icon_id": "23343190",
+      "name": "供应商",
+      "font_class": "menu-gongyingshang2",
+      "unicode": "e68b",
+      "unicode_decimal": 59019
+    },
+    {
+      "icon_id": "23343196",
+      "name": "福利",
+      "font_class": "menu-fuli",
+      "unicode": "e691",
+      "unicode_decimal": 59025
+    },
+    {
+      "icon_id": "23343202",
+      "name": "品牌管理",
+      "font_class": "menu-pinpaiguanli",
+      "unicode": "e697",
+      "unicode_decimal": 59031
+    },
+    {
+      "icon_id": "23943134",
+      "name": "时间",
+      "font_class": "dmp-time",
+      "unicode": "f96a",
+      "unicode_decimal": 63850
+    },
+    {
+      "icon_id": "3862612",
+      "name": "折线面积图 ",
+      "font_class": "dmp-lin",
+      "unicode": "e618",
+      "unicode_decimal": 58904
+    },
+    {
+      "icon_id": "7587752",
+      "name": "统计图",
+      "font_class": "dmp-bar",
+      "unicode": "e60a",
+      "unicode_decimal": 58890
+    },
+    {
+      "icon_id": "14146193",
+      "name": "业绩面板",
+      "font_class": "dmp-card",
+      "unicode": "e639",
+      "unicode_decimal": 58937
+    },
+    {
+      "icon_id": "16656969",
+      "name": "统计图",
+      "font_class": "dmp-pie",
+      "unicode": "e726",
+      "unicode_decimal": 59174
+    },
+    {
+      "icon_id": "19701146",
+      "name": "图钉",
+      "font_class": "pushpin-fill",
+      "unicode": "eba2",
+      "unicode_decimal": 60322
+    },
+    {
+      "icon_id": "15644380",
+      "name": "图钉",
+      "font_class": "tuding",
+      "unicode": "e6ca",
+      "unicode_decimal": 59082
+    },
+    {
+      "icon_id": "29322330",
+      "name": "palette",
+      "font_class": "menu-palette",
+      "unicode": "e605",
+      "unicode_decimal": 58885
+    },
+    {
+      "icon_id": "29322328",
+      "name": "customer",
+      "font_class": "menu-customer",
+      "unicode": "e604",
+      "unicode_decimal": 58884
+    },
+    {
+      "icon_id": "29322327",
+      "name": "dmp",
+      "font_class": "menu-dmp",
+      "unicode": "e603",
+      "unicode_decimal": 58883
+    },
+    {
+      "icon_id": "29322300",
+      "name": "role",
+      "font_class": "role",
+      "unicode": "e602",
+      "unicode_decimal": 58882
+    }
+  ]
+}

BIN
src/assets/iconsvg/iconfont.ttf


BIN
src/assets/iconsvg/iconfont.woff


BIN
src/assets/iconsvg/iconfont.woff2


BIN
src/assets/images/error-image.png


BIN
src/assets/images/login-banner.png


+ 1 - 0
src/assets/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 176 - 0
src/components/FindHead/index.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="filter-box"  @keydown="handleKeyDown">
+    <div class="slot-header">
+      <slot name="header"></slot>
+    </div>
+    <div v-for="item in props.list" :key="item.key" class="filter-box-row">
+      <div :class="item.label?'filter-box-label':'filter-box-label-null'">{{ item.label }}</div>
+      <a-input
+        v-if="item.type == 'input'"
+        v-model="refData.findData[item.key]"
+        class="filter-box-input"
+        :placeholder="item.placeholder"
+        @input="(e) => onChangeFilterNumber(e, item)"
+      />
+
+      <a-select
+        v-if="item.type == 'select'"
+        v-model="refData.findData[item.key]"
+        class="filter-box-input"
+        style="width: 240px"
+        clearable
+        :placeholder="item.placeholder"
+        :default-value="item.defaultValue"
+        :multiple="item.multiple"
+        allow-clear
+      >
+      
+        <a-option
+          v-for="cItem in item.options"
+          :key="cItem[item?.keys?.key || 'key']"
+          :label="cItem[item?.keys.label || 'label']"
+          :value="cItem[item?.keys.key || 'key']"
+        />
+      </a-select>
+      <a-radio-group
+        v-if="item.type == 'radio'"
+        v-model="refData.findData[item.key]"
+        style="width: 280px"
+        @change="changeEvent(item.hasEvent)"
+      >
+        <a-radio v-for="cItem in item.options" :key="cItem.value" :value="cItem[item.value]">{{
+          cItem[item.label]
+        }}</a-radio>
+      </a-radio-group>
+
+      <a-range-picker
+        v-if="item.type == 'date-picker'"
+        v-model="refData.findData[item.key]"
+        class="filter-box-input"
+        style="max-width: 240px"
+        :show-time="!item.showTime"
+        :time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
+        :format="item.showTime ? 'YYYY-MM-DD' : ' YYYY-MM-DD HH:mm:ss'"
+        @change="
+          (e) => {
+            evPicker(e, item.keys)
+          }
+        "
+      />
+    </div>
+    <div class="filter-box-row">
+      <a-button class="btn-open" type="primary" size="mini" @click="evInit">查询</a-button>
+      <a-button class="btn-open" size="mini" @click="evReset">重置</a-button>
+      <slot name="footer"></slot>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive } from 'vue'
+
+const props = defineProps({
+  list: {
+    type: Array,
+    required: true,
+  },
+})
+
+const emit = defineEmits(['onSearch'])
+const refData = reactive({
+  findData: {},
+})
+
+const evInit = () => {
+  emit('onSearch', refData.findData)
+}
+const handleKeyDown = (event)  =>{
+  if (event.code === 'Enter' || event.keyCode === 13) {
+    emit('onSearch', refData.findData)
+  }
+}
+
+const changeEvent = (hasEvent) => {
+  if (hasEvent) evInit()
+}
+const initDefault = () => {
+  props.list.forEach((item) => {
+    if (item?.defaultValue?.length > 0) {
+      refData.findData[item.key] = item?.defaultValue
+    }
+  })
+}
+const onChangeFilterNumber = (value, item) => {
+  if (item.verification && item.verification === 'number') {
+    refData.findData[item.key] = value.replace(/[^0-9,]/gi, '')
+  }
+}
+const evReset = () => {
+  refData.findData = {}
+  initDefault()
+  evInit()
+}
+const evPicker = (dateString, keys) => { 
+  refData.findData[keys[0]] = dateString[0]
+  refData.findData[keys[1]] = dateString[1]
+}
+onMounted(() => {
+  initDefault()
+})
+</script>
+
+<style lang="less" scoped>
+.filter-box {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 0.8rem 0;
+  margin: 0.8rem 0;
+  background-color: @black_2;
+
+  &:last-of-type {
+    margin-bottom: 0;
+  }
+
+  .filter-box-row {
+    display: flex;
+    align-items: center;
+    margin: 0.2rem 0;
+  }
+
+  .filter-box-label {
+    text-align: right;
+    min-width: 0;
+    max-width: 6rem;
+    margin:0 1rem;
+  }
+  .filter-box-label-null{
+    text-align: right;
+    min-width: 0rem;
+    max-width: 0rem;
+    margin-right: 0.5rem;
+  }
+
+  .filter-box-input {
+    min-width: 240px;
+    max-width: 240px;
+  }
+  .btn-open {
+    margin-left: 1rem;
+  }
+}
+.slot-header {
+  width: 100%;
+}
+:deep(.arco-input-wrapper) {
+  background-color: @bg_color_2;
+}
+:deep(.arco-select-view-single) {
+  background-color: @bg_color_2;
+}
+:deep(.arco-picker) {
+  background-color: @bg_color_2;
+}
+:deep(.arco-select-view-multiple.arco-select-view-size-medium) {
+  background-color: @bg_color_2;
+}
+</style>

+ 30 - 0
src/components/Layout/Layout.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="app-container-layout">
+    <Layout />
+  </div>
+</template>
+
+<script setup>
+import { onMounted, computed } from "vue"
+import LayoutComponents from "./components"
+import { useSystemStore } from '@/store/modules/systemStore'
+
+const systemStore = useSystemStore()
+
+const Layout = computed(()=>{
+  return LayoutComponents[systemStore.getLayout]
+})
+
+</script>
+
+<style scoped lang="less">
+.app-container-layout {
+	width: 100%;
+	height: 100vh;
+  margin: 0;
+	max-height: 100vh;
+	display: flex;
+	flex-direction: row;
+  overflow: hidden;
+}
+</style>

+ 6 - 0
src/components/Layout/components/index.js

@@ -0,0 +1,6 @@
+import blendLeft from './layout/blendLeft.vue'
+
+
+export default {
+  blendLeft
+}

+ 303 - 0
src/components/Layout/components/layout/blendLeft.vue

@@ -0,0 +1,303 @@
+<template>
+  <a-layout style="min-height: 100vh">
+    <a-layout-sider
+      theme="dark"
+      :width="140"
+      :collapsed-width="48"
+      collapsible
+      :trigger="null"
+      @collapse="evMenuSecondLongShow"
+      v-model:collapsed="menuSecondLongShow"
+    >
+      <div class="logo-layout">
+        <div class="logo">
+          <img src="@/assets/vite.svg" />
+          <h3 v-if="!menuSecondLongShow">Admin Pro</h3>
+        </div>
+      </div>
+
+      <a-menu
+        v-model:selectedKeys="menuTabSate"
+        theme="dark"
+        mode="inline"
+        @mouseleave="evMouseleaveMenu"
+      >
+        <a-menu-item
+          v-for="routeItem in routesData"
+          :key="routeItem.name"
+          @mouseenter="evMenuGetFn(routeItem, 'mouseenter')"
+          @click="changeRoutesItems(routeItem)"
+        >
+          <template #icon>
+            <svg-icon :icon="routeItem.meta.icon"></svg-icon>
+          </template>
+          <span>{{ routeItem.meta.title }}</span>
+        </a-menu-item>
+      </a-menu>
+    </a-layout-sider>
+
+    <a-layout>
+      <a-layout-header class="layout-header">
+        <LayoutHeader />
+      </a-layout-header>
+      <a-layout>
+        <a-layout-sider
+          id="layout-sider"
+          :collapsed="false"
+          :width="
+            menuSecondData &&
+            menuSecondData.children &&
+            menuSecondData.children.length > 0
+              ? 160
+              : 0
+          "
+          @mouseleave="evMouseLeavesSubMenu"
+        >
+          <a-menu
+            :selectedKeys="routeItemSelectedKeys"
+            id="layout-sider"
+            theme="light"
+            mode="vertical"
+            :collapsed="false"
+            :auto-open="true"
+          >
+            <template v-for="routeItem in menuSecondData?.children || []">
+              <!-- {{ routeItem }} -->
+              <a-menu-item
+                v-if="!routeItem.children || routeItem.children.length === 0"
+                :key="routeItem.name?.toString()"
+                @click="evGoPage(routeItem)"
+              >
+                <span class="menu-level-font">{{ routeItem.meta.title }}</span>
+              </a-menu-item>
+
+              <template
+                v-else-if="routeItem.children && routeItem.children.length > 0"
+              >
+                <sub-menu
+                  :key="routeItem.name"
+                  :menu-info="routeItem"
+                  @go="evGoPage"
+                />
+              </template>
+            </template>
+          </a-menu>
+        </a-layout-sider>
+
+        <main class="layout-content-main">
+          <router-view v-slot="{ Component }" class="layout-content-router">
+            <component :is="Component" key="Layout" />
+          </router-view>
+        </main>
+      </a-layout>
+    </a-layout>
+  </a-layout>
+</template>
+<script setup>
+import { ref, computed, h, reactive, onMounted, watch, nextTick } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { useSystemStore } from "@/store/modules/systemStore";
+import LayoutHeader from "@/components/Layout/components/layoutHeader/index.vue";
+import subMenu from "@/components/Layout/components/subMenu/index.vue";
+const systemStore = useSystemStore();
+
+const route = useRoute();
+const router = useRouter();
+
+const menuSecondLongShow = ref(systemStore.menuSecondLongShow);
+// 主菜单
+const routesData = router.options.routes[0]?.children || [];
+
+const menuTabSate = ref([systemStore.getMenuTabSate || routesData[0].name]);
+//子菜单
+const menuSecondData = ref(systemStore.getRouteItem);
+// 选中的状态路由列表
+const menuSecondSelectedStatusData = ref(systemStore.getRouteItem);
+const routeItemSelectedKeys = ref();
+
+const evMenuSecondLongShow = () => {
+  systemStore.setStateValue({
+    key: "menuSecondLongShow",
+    value: menuSecondLongShow.value ? 1 : 0,
+    localStorage: true,
+  });
+};
+const evMouseleaveMenu = (e) => {
+  if (
+    e.relatedTarget.offsetParent.id &&
+    e.relatedTarget.offsetParent.id == "layout-sider"
+  )
+    return;
+  menuSecondData.value = menuSecondSelectedStatusData.value;
+};
+// 子菜单
+const evMouseLeavesSubMenu = () => {
+  menuSecondData.value = menuSecondSelectedStatusData.value;
+};
+const evMenuGetFn = (routeItem, type) => {
+  menuSecondData.value = routeItem;
+};
+
+// 查找最下级
+const findPath = (data) => {
+  if (data.children && data.children.length > 0) {
+    return findPath(data.children[0]);
+  } else {
+    return data;
+  }
+};
+
+// 主路由 状态
+const changeRoutesItems = (e) => {
+  const item = findPath(e);
+  evGoPage(item);
+};
+
+// 跳转路由
+const evGoPage = async (routeItem) => {
+  await router.push({
+    name: routeItem.name,
+  });
+  menuTabSate.value = [route.matched[1].name];
+  systemStore.setStateValue({
+    key: "menuTabSate",
+    value: menuTabSate.value[0] || "",
+    localStorage: true,
+  });
+
+  menuSecondSelectedStatusData.value = routesData.find(
+    (item) => item.name == menuTabSate.value
+  );
+  menuSecondData.value = menuSecondSelectedStatusData.value;
+  systemStore.setStateValue({
+    key: "routeItem",
+    value: JSON.stringify(menuSecondData.value),
+    localStorage: true,
+  });
+};
+
+watch(
+  route,
+  (val) => {
+    if (val.name) {
+      routeItemSelectedKeys.value = [val.name];
+    }
+  },
+  { immediate: true }
+);
+</script>
+<style scoped lang="less">
+.layout-header {
+  height: 48px;
+  background: @bg_color_2;
+  padding-inline: 10px;
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+  z-index: 9;
+}
+.ant-layout-content {
+  height: auto;
+  min-height: auto;
+  overflow-y: auto;
+  padding: 0 20px;
+}
+.layout-content-main {
+  flex: 1;
+  padding: 0 1.2rem 1.6rem 1.2rem;
+  margin: 1.1rem 0 0rem 0;
+  overflow-y: auto;
+  overflow: overlay;
+  height: 95%;
+  display: flex;
+  flex-wrap: wrap;
+}
+.logo-layout {
+  height: 64px;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .logo {
+    width: 100%;
+    height: 32px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    h3 {
+      white-space: nowrap;
+    }
+  }
+}
+.arco-layout {
+  color: @text_color_1;
+  background: @layout-split-color !important;
+  overflow: hidden;
+}
+:deep(.arco-menu-light) {
+  background-color: @bg_color_3;
+  .arco-menu-item {
+    background-color: @bg_color_3;
+  }
+}
+:deep(.arco-layout-sider-light) {
+  background-color: @bg_color_3;
+}
+:deep(.arco-menu-inline-header) {
+  font-weight: 600;
+  color: @text_color_1;
+  background-color: @bg_color_3;
+}
+
+:deep(.layout-content-router) {
+  padding: 0 1rem 1rem 1rem;
+  background-color: @bg_color_4;
+  box-sizing: border-box;
+  width: 100%;
+}
+:deep(.arco-menu-inline-content) {
+  .arco-menu-selected {
+    color: @text_color_1;
+    background-color: @black_3;
+    &:after {
+      content: none;
+    }
+  }
+}
+#layout-sider {
+  :deep(.arco-menu-selected) {
+    color: @text_color_1 !important;
+    font-weight: bold;
+    background-color: @black_3;
+    .arco-icon {
+      color: @text_color_1 !important;
+    }
+    &:hover {
+      background-color: rgba(235, 19, 19, 0.1) !important;
+    }
+  }
+  :deep(.arco-menu-inline) {
+    .arco-menu-inline-header {
+      background-color: @bg_color_3;
+      padding-left: 20px;
+      left: 5px;
+      svg {
+        color: @text_color_1 !important;
+        font-size: 14px;
+      }
+      .arco-menu-icon-suffix {
+        color: @text_color_1 !important;
+        left: 0px !important;
+        right: auto;
+      }
+      &:hover {
+        background-color: rgba(255, 255, 255, 0) !important;
+      }
+    }
+    .arco-menu-item {
+      padding: 0 5px;
+    }
+  }
+  :deep(.arco-menu-item) {
+    padding-left: 24px;
+  }
+}
+</style>

+ 110 - 0
src/components/Layout/components/layoutHeader/index.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="header">
+    <a-select :style="{width:'100px'}" v-model="langStore.lang"  :bordered="false" @change="toggleLang">
+      <a-option v-for="item in langList" :key="item.key" :value="item.key">{{item.label}}</a-option>
+    </a-select>
+
+    <div class="toggle-theme-box">
+      <icon-sun-fill v-if="designStore.getDarkTheme"  @click="toggleTheme(false)" :size="18"/>
+      <icon-moon-fill  v-else  @click="toggleTheme(true)"  :size="18"/>
+    </div>
+
+    <a-dropdown trigger="click">
+      <a-avatar :size="38" class="avatar-body">
+        <img
+          class="avatar-image"
+          alt="avatar"
+          src="https://avatars.githubusercontent.com/u/39849555?v=4"
+        />
+      </a-avatar>
+      <template #content>
+        <a-doption>
+          <a-space @click="evHandleLogout">
+            <icon-export />
+            <span>{{ $t('global.logOut') }}</span>
+          </a-space>
+        </a-doption>
+      </template>
+    </a-dropdown>
+
+
+  </div>
+</template>
+
+<script setup>
+import { ref,inject } from 'vue'
+import { useRouter } from 'vue-router'
+import { useSystemStore } from '@/store/modules/systemStore'
+import { Notification } from '@arco-design/web-vue'
+import { useDesignStore } from '@/store/modules/designStore'
+import { useLangStore } from '@/store/modules/langStore'
+import { useI18n } from 'vue-i18n'
+import { langList } from '@/i18n'
+import { fn_logout } from '@/utils'
+
+import { updateRouteByMenu  } from "@/router/router.update.js"
+
+ 
+const router = useRouter()
+const { locale } = useI18n()
+const systemStore = useSystemStore()
+const designStore = useDesignStore()
+const langStore = useLangStore()
+
+
+// const reload  = inject('reloadRoutePage')
+  
+const toggleTheme  = (e) =>{
+  designStore.changeTheme(e)
+}
+
+const toggleLang = async (e) =>{
+  langStore.changeLang(e) 
+  locale.value = langStore.lang
+  await updateRouteByMenu(router, systemStore)
+  router.go(0)
+}
+
+const evHandleLogout = () => {
+  
+    Notification.success({
+      content: '退出成功!',
+      duration: 1000,
+    })
+    // 清除缓存数据
+    fn_logout()
+
+    router.push({
+      name: "login",
+    })
+}
+
+</script>
+
+<style scoped lang="less">
+.header{
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding: 0 10px;
+}
+.avatar-body{
+  :deep(.arco-avatar-image){
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .avatar-image{
+    border-radius: 50%;
+    width: 34px;
+    height: 34px;
+  }
+}
+.toggle-theme-box{
+  margin-right: 0.8rem;
+}
+:deep(.arco-select-view-suffix){
+  padding: 0;
+}
+</style>

+ 33 - 0
src/components/Layout/components/subMenu/index.vue

@@ -0,0 +1,33 @@
+<template>
+  <a-sub-menu>
+    <template #title>
+      <span>{{ props.menuInfo.meta.title }}</span>
+    </template>
+        <a-menu-item
+          v-for="item in props.menuInfo.children"
+          :key="item.name"
+          class="a-menu-item"
+          @click="evGoPage(item)"
+        >
+          <span>{{  item.meta.title }}</span>
+        </a-menu-item>
+  </a-sub-menu>
+</template>
+
+<script setup>
+const props = defineProps({
+  menuInfo: {
+    type: Object,
+    required: true,
+  },
+})
+
+const emit = defineEmits(['go'])
+
+const evGoPage = (data) => {
+  emit('go', data)
+}
+defineExpose({
+  evGoPage,
+})
+</script>

+ 113 - 0
src/components/MonacoEditor/index.vue

@@ -0,0 +1,113 @@
+<template>
+  <div ref="editorContainer" class="editor-container"></div>
+</template>
+
+<script setup>
+import * as monaco from "monaco-editor";
+import { ref, watch, onMounted, onBeforeUnmount } from "vue";
+
+import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
+import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
+import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
+import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
+import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
+
+self.MonacoEnvironment = {
+  getWorker(_, label) {
+    if (label === "json") {
+      return new jsonWorker();
+    }
+    if (["css", "scss", "less"].includes(label)) {
+      return new cssWorker();
+    }
+    if (["html", "handlebars", "razor"].includes(label)) {
+      return new htmlWorker();
+    }
+    if (["typescript", "javascript"].includes(label)) {
+      return new tsWorker();
+    }
+    return new EditorWorker();
+  },
+};
+
+const props = defineProps({
+  language: {
+    type: String,
+    default: 'json'
+  },
+  modelValue: {
+    type: String,
+    default: ''
+  },
+});
+
+let editor;
+const editorContainer = ref(null);
+const emit = defineEmits(['update:modelValue']);
+
+watch(() => props.modelValue, (newValue) => {
+  if (editor) {
+    const value = editor.getValue();
+    if (newValue !== value) {
+        editor.setValue(newValue);
+      }
+    }
+  }
+);
+
+
+
+
+onMounted(() => {
+  editor = monaco.editor.create(editorContainer.value, {
+    value: props.modelValue,
+    language:  props.language,
+    theme: 'vs',
+    folding: true, // 是否折叠
+    foldingHighlight: true, // 折叠等高线
+    foldingStrategy: "indentation", // 折叠方式  auto | indentation
+    showFoldingControls: "always", // 是否一直显示折叠 always | mouseover
+    disableLayerHinting: true, // 等宽优化
+    emptySelectionClipboard: false, // 空选择剪切板
+    selectionClipboard: false, // 选择剪切板
+    automaticLayout: true, // 自动布局
+    codeLens: false, // 代码镜头
+    scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
+    colorDecorators: true, // 颜色装饰器
+    accessibilitySupport: "off", // 辅助功能支持  "auto" | "off" | "on"
+    lineNumbers: "on", // 行号 取值: "on" | "off" | "relative" | "interval" | function
+    lineNumbersMinChars: 5, // 行号最小字符   number
+    readOnly: false, //是否只读  取值 true | false
+    tabSize: 2,
+    roundedSelection: true,
+    // 滚动条
+    scrollbar: {
+      verticalScrollbarSize: 8,
+      horizontalScrollbarSize: 8,
+    },
+  });
+  // 监听值的变化
+  editor.onDidChangeModelContent(() => {
+    const value = editor.getValue() //给父组件实时返回最新文本
+    console.log(value)
+    emit('update:modelValue', value)
+    // emit('change', value)
+  })
+
+});
+
+onBeforeUnmount(() => {
+  // 卸载IDE
+  editor.dispose();
+});
+</script>
+
+<style lang="less" scoped>
+.editor-container {
+  height: 150px;
+  width: 100%;
+  border: 1px solid #cecece;
+  padding: 20px 10px;
+  box-sizing: border-box;
+}
+</style>

+ 43 - 0
src/components/Svg-icon/SvgIcon.vue

@@ -0,0 +1,43 @@
+<template>
+  <svg :class="svgClass" :aria-hidden="true">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  iconClass: {
+    type: String,
+  },
+  icon: {
+    // iconfont 库
+    type: String,
+  },
+  className: {
+    type: String,
+    default: '',
+  },
+})
+
+const iconName = computed(() => {
+  return props.icon ? `#${props.icon}` : `#icon-${props.iconClass}`
+})
+const svgClass = computed(() => {
+  if (props.className) {
+    return `svg-icon${props.className}`
+  }
+  return 'svg-icon'
+})
+</script>
+
+<style scoped lang="less">
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>

+ 2 - 0
src/hooks/index.js

@@ -0,0 +1,2 @@
+export * from '@/hooks/useLang.hook'
+export * from '@/hooks/useTheme.hook'

+ 26 - 0
src/hooks/useLang.hook.js

@@ -0,0 +1,26 @@
+import { computed } from 'vue'
+import { useLangStore } from '@/store/modules/langStore'
+
+
+import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
+import enUS from '@arco-design/web-vue/es/locale/lang/en-us';
+import thTH from '@arco-design/web-vue/es/locale/lang/th-th';
+
+const i18n = {
+  "zh-CN": zhCN,
+  "en-US": enUS,
+  "th-TH": thTH,
+}
+
+// 语言切换
+export const useLang = () => {
+  const lang = useLangStore()
+  
+  const locale = computed(() => {
+    return  i18n[lang.getLang]
+  })
+
+  return {
+    locale
+  }
+}

+ 12 - 0
src/hooks/useTheme.hook.js

@@ -0,0 +1,12 @@
+import { useDesignStore } from '@/store/modules/designStore'
+
+export const useDarkThemeHook = () => {
+  const designStore = useDesignStore()
+  if(designStore.getDarkTheme){
+    // 设置为暗黑主题
+    document.body.removeAttribute('arco-theme');
+  }else{
+    // 恢复亮色主题
+    document.body.setAttribute('arco-theme', 'dark')
+  }
+}

+ 55 - 0
src/i18n/en/dataPackage.js

@@ -0,0 +1,55 @@
+export default {
+    // Data package management
+    addDataPackage: 'Add Data Package',
+    editDataPackage: 'Edit Data Package',
+    packageName: 'Package Name',
+    enterPackageName: 'Please enter package name',
+    operatorType: 'Operator Type',
+    selectOperatorType: 'Please select operator type',
+    searchExecuted: 'Search executed',
+    searchReset: 'Search conditions have been reset',
+    selectStatus: 'Please select status',
+    statusName: 'Status',
+    
+    // Table columns
+    id: 'ID',
+    packageCode: 'Package Code',
+    operatorName: 'Operator Name',
+    packageSize: 'Package Size',
+    standardPrice: 'Standard Price',
+    status: 'Status',
+    
+    // Operation tips
+    packageDeleted: 'Data package {name} has been deleted',
+    packageUpdated: 'Data package {name} has been updated',
+    packageAdded: 'Data package {name} has been added',
+    
+    // Form fields
+    operatorPackageCode: 'Operator Package Code',
+    enterOperatorPackageCode: 'Please enter operator package code',
+    enterPackageSize: 'Please enter package size',
+    enterStandardPrice: 'Please enter standard price',
+    
+    // Status options
+    status: {
+      normal: 'Normal',
+      disabled: 'Disabled',
+      offline: 'Offline'
+    },
+    
+    // Operator type options
+    operatorTypes: {
+      chinamobile: 'China Mobile',
+      chinaunicom: 'China Unicom',
+      chinatelecom: 'China Telecom',
+      international: 'International',
+      foreign_local: 'Foreign Local'
+    },
+    
+    // Form validation messages
+    packageNameRequired: 'Please enter package name',
+    operatorTypeRequired: 'Please select operator type',
+    statusRequired: 'Please select status',
+    packageSizeRequired: 'Please enter package size',
+    standardPriceRequired: 'Please enter standard price'
+  };

+ 63 - 0
src/i18n/en/form.js

@@ -0,0 +1,63 @@
+export default {
+  "Key": "id",
+  "Id": "Operation",
+  "Delete": "Delete",
+  "DeleteConfirm": "Are you sure you want to delete this information?",
+  "Edit": "Edit",
+  "Add": "Add",
+  "Cancel": "Cancel",
+  "Confirm": "Confirm",
+  "Search": "Search",
+  "Reset": "Reset",
+  "Export": "Export",
+  "Import": "Import",
+  "Status": "Status",
+  "ExportSuccess": "Export successful",
+  "ImportSuccess": "Import successful",
+  "ExportFailed": "Export failed",
+  "ImportFailed": "Import failed",
+  "ExportConfirm": "Are you sure you want to export?",
+  "ImportConfirm": "Are you sure you want to import?",
+  "PleaseEnterThe": "Please enter",
+
+  "DBdistributorSource": "DB Source",
+
+  // Menu form
+  "Name": "Menu Name",
+  "Icon": "Icon",
+  "SortNumber": "Sort Number",
+  "Refresh": "Cache",
+  "Path": "Identifier",
+  "Url": "Route",
+
+  // Dictionary
+  "dictionaryName": "Dictionary Name",
+  "dictionaryType": "Dictionary Type",
+  "dictionaryValue": "Value",
+  "dictionarySortNumber": "Sort Number",
+  "dictionaryDescription": "Description",
+  "createUser": "Created By",
+  "updateUser": "Updated By",
+  "createTime": "Creation Time",
+  "updateTime": "Update Time",
+
+  // Distributor
+  "distributorName": "Distributor Name",
+  "distributorContact": "Contact Person",
+  "distributorPhone": "Contact Phone",
+
+  // Data pool form
+  "datapoolForm": {
+    "title": "Create SIM Card Pool",
+    "dealer": "Dealer",
+    "poolName": "Pool Name",
+    "operator": "Operator",
+    "planType": "Plan Type",
+    "displayFlow": "Display Flow",
+    "plan": "Plan",
+    "pleaseSelect": "Please select",
+    "pleaseEnter": "Please enter",
+    "submit": "Submit",
+    "cancel": "Cancel"
+  },
+}

+ 11 - 0
src/i18n/en/global.js

@@ -0,0 +1,11 @@
+export default {
+  "logOut": "log out",
+  "newMenu": "new menu",
+  common: {
+    search: 'Search',
+    reset: 'Reset',
+    edit: 'Edit',
+    delete: 'Delete',
+    operations: 'Operations'
+  }
+}

+ 15 - 0
src/i18n/en/index.js

@@ -0,0 +1,15 @@
+import login from './login'
+import global from './global'
+import form from './form'
+import supplier from './supplier'
+import plan from './plan'
+import dataPackage from './dataPackage'
+
+export default {
+  login,
+  global,
+  form,
+  supplier,
+  plan,
+  dataPackage
+}

+ 3 - 0
src/i18n/en/login.js

@@ -0,0 +1,3 @@
+export default {
+  'Welcome': 'Welcome',
+}

+ 54 - 0
src/i18n/en/plan.js

@@ -0,0 +1,54 @@
+export default {
+    feeName: 'Fee Name',
+    enterFeeName: 'Enter fee name or code',
+    operatorType: 'Operator Type',
+    selectOperatorType: 'Please select',
+    addFee: 'Add Fee',
+    editFee: 'Edit Fee',
+    id: 'ID',
+    feeCode: 'Fee Code',
+    supplierName: 'Supplier Name',
+    dataType: 'Data Type',
+    billingCategory: 'Billing Category',
+    billingCycle: 'Billing Cycle',
+    packageSize: 'Package Size',
+    standardPrice: 'Standard Price',
+    cycleLimitation: 'Cycle Limitation',
+    status: {
+      normal: 'Normal',
+      disabled: 'Disabled',
+      offline: 'Offline'
+    },
+    operatorTypes: {
+      general: 'General',
+      specific: 'Specific'
+    },
+    searchExecuted: 'Search executed',
+    feeDeleted: 'Fee {name} has been deleted',
+    feeUpdated: 'Fee {name} has been updated',
+    feeAdded: 'New fee {name} has been added',
+    basicInfo: 'Basic Information',
+    supplier: 'Supplier',
+    selectSupplier: 'Please select supplier',
+    supplier1: 'Supplier 1',
+    supplier2: 'Supplier 2',
+    general: 'General',
+    specific: 'Specific',
+    data: 'Data',
+    voice: 'Voice',
+    nb: 'NB',
+    sms: 'SMS',
+    billingInfo: 'Billing Information',
+    feeCodeRequired: 'Please enter fee code',
+    feeNameRequired: 'Please enter fee name',
+    supplierRequired: 'Please select supplier',
+    dataTypeRequired: 'Please select data type',
+    billingCategoryRequired: 'Please select billing category',
+    statusRequired: 'Please select status',
+    billingCycleRequired: 'Please select billing cycle',
+    packageSizeRequired: 'Please enter package size',
+    standardPriceRequired: 'Please enter standard price',
+    minPeriodRequired: 'Please enter minimum period',
+    maxPeriodRequired: 'Please enter maximum period',
+    fillRequiredFields: 'Please fill in all required fields'
+  }

+ 85 - 0
src/i18n/en/supplier.js

@@ -0,0 +1,85 @@
+export default {
+    addSupplier: 'Add Supplier',
+    editSupplier: 'Edit Supplier',
+    supplierName: 'Supplier Name',
+    enterSupplierName: 'Please enter supplier name',
+    operatorType: 'Operator Type',
+    selectOperatorType: 'Please select operator type',
+    id: 'ID',
+    operatorCode: 'Operator Code',
+    operatorName: 'Operator Name',
+    updateTime: 'Update Time',
+    searchExecuted: 'Search executed',
+    searchReset: 'Search conditions have been reset',
+    operatorTypes: {
+      foreign_local: 'Foreign Local',
+      international: 'International',
+      chinamobile: 'China Mobile',
+      chinaunicom: 'China Unicom',
+      chinatelecom: 'China Telecom'
+    },
+    basicInfo: 'Basic Information',
+    operatorApiConfig: 'Operator API Configuration',
+    advancedConfig: 'Advanced Configuration',
+    apiTemplate: 'API Template',
+    selectApiTemplate: 'Select API Template',
+    apiTemplates: {
+      telecom_CMP: 'Telecom CMP'
+    },
+    apiUrl: 'API URL',
+    enterApiUrl: 'Enter API URL',
+    realNameAuthUrl: 'Real-name Authentication URL',
+    enterRealNameAuthUrl: 'Enter real-name authentication URL',
+    realNameUrlParam: 'Real-name URL Parameter',
+    enterRealNameUrlParam: 'Enter real-name URL parameter',
+    appKey: 'App Key',
+    enterAppKey: 'Enter App Key',
+    apiSecret: 'API Secret',
+    enterApiSecret: 'Enter API Secret',
+    key: 'Key',
+    enterKey: 'Enter Key',
+    stopCardAction: 'Stop Card Action',
+    selectStopCardAction: 'Select stop card action',
+    stopCardActions: {
+      suspend: 'Suspend'
+    },
+    needManualRealName: 'Need Manual Real-name',
+    need: 'Need',
+    noNeed: 'No Need',
+    autoSyncUsage: 'Auto Sync Usage',
+    enable: 'Enable',
+    disable: 'Disable',
+    useOperatorApi: 'Use Operator API',
+    call: 'Call',
+    noCall: 'No Call',
+    setQuantityLimit: 'Set Quantity Limit',
+    quantityLimit: 'Quantity Limit',
+    noQuantityLimit: 'No Quantity Limit',
+    useOperatorDataPool: 'Use Operator Data Pool',
+    silentPeriod: 'Silent Period',
+    enterSilentPeriod: 'Enter silent period',
+    unusedSuspensionDays: 'Unused Suspension Days',
+    enterDays: 'Enter days',
+    maxWhitelistAdditions: 'Max Whitelist Additions',
+    enterTimes: 'Enter times',
+    totalActiveWhitelists: 'Total Active Whitelists',
+    enterCount: 'Enter count',
+    dailyWhitelistRemovals: 'Daily Whitelist Removals',
+    whitelistAddOrder: 'Whitelist Add Order',
+    selectOrder: 'Select order',
+    noWhitelist: 'No Whitelist',
+    numberGenerationRule: 'Number Generation Rule',
+    enterNumberGenerationRule: 'Enter number generation rule',
+    supplierNameRequired: 'Supplier name is required',
+    operatorTypeRequired: 'Operator type is required',
+    apiTemplateRequired: 'API template is required',
+    apiUrlRequired: 'API URL is required',
+    appKeyRequired: 'App Key is required',
+    apiSecretRequired: 'API Secret is required',
+    needManualRealNameRequired: 'Need manual real-name is required',
+    autoSyncUsageRequired: 'Auto sync usage is required',
+    useOperatorApiRequired: 'Use operator API is required',
+    useOperatorDataPoolRequired: 'Use operator data pool is required',
+    silentPeriodRequired: 'Silent period is required',
+    fillRequiredFields: 'Please fill in all required fields'
+  }

+ 38 - 0
src/i18n/index.js

@@ -0,0 +1,38 @@
+//语言
+import { lang } from '@/settings/designSetting'
+import { createI18n } from 'vue-i18n' //引入vue-i18n组件
+import { useLangStore } from '@/store/modules/langStore'
+import zh from './zh/index'
+import en from './en/index'
+import th from './th/index'
+
+
+
+ 
+const langStore = useLangStore()
+
+export const i18n = createI18n({
+  globalInjection:true,
+  locale: langStore?.lang || lang,
+  messages: {
+    'zh-CN': zh,
+    'en-US': en,
+    'th-TH': th
+  }
+})
+
+// 语言数组
+export const langList = [
+  {
+    label: '简体中文',
+    key: 'zh-CN'
+  },
+  {
+    label: 'English',
+    key: 'en-US'
+  },
+  {
+    label: 'แบบไทย',
+    key: 'th-TH'
+  }
+]

+ 55 - 0
src/i18n/th/dataPackage.js

@@ -0,0 +1,55 @@
+export default {
+    // การจัดการแพ็กเกจข้อมูล
+    addDataPackage: 'เพิ่มแพ็กเกจข้อมูล',
+    editDataPackage: 'แก้ไขแพ็กเกจข้อมูล',
+    packageName: 'ชื่อแพ็กเกจ',
+    enterPackageName: 'กรุณาป้อนชื่อแพ็กเกจ',
+    operatorType: 'ประเภทผู้ให้บริการ',
+    selectOperatorType: 'กรุณาเลือกประเภทผู้ให้บริการ',
+    searchExecuted: 'ดำเนินการค้นหาแล้ว',
+    searchReset: 'รีเซ็ตเงื่อนไขการค้นหาแล้ว',
+    selectStatus: 'กรุณาเลือกสถานะ',
+    statusName: 'สถานะ',
+    
+    // คอลัมน์ตาราง
+    id: 'รหัส',
+    packageCode: 'รหัสแพ็กเกจ',
+    operatorName: 'ชื่อผู้ให้บริการ',
+    packageSize: 'ขนาดแพ็กเกจ',
+    standardPrice: 'ราคามาตรฐาน',
+    status: 'สถานะ',
+    
+    // ข้อความการดำเนินการ
+    packageDeleted: 'ลบแพ็กเกจข้อมูล {name} แล้ว',
+    packageUpdated: 'อัปเดตแพ็กเกจข้อมูล {name} แล้ว',
+    packageAdded: 'เพิ่มแพ็กเกจข้อมูล {name} แล้ว',
+    
+    // ฟิลด์แบบฟอร์ม
+    operatorPackageCode: 'รหัสแพ็กเกจผู้ให้บริการ',
+    enterOperatorPackageCode: 'กรุณาป้อนรหัสแพ็กเกจผู้ให้บริการ',
+    enterPackageSize: 'กรุณาป้อนขนาดแพ็กเกจ',
+    enterStandardPrice: 'กรุณาป้อนราคามาตรฐาน',
+    
+    // ตัวเลือกสถานะ
+    status: {
+      normal: 'ปกติ',
+      disabled: 'ปิดใช้งาน',
+      offline: 'ออฟไลน์'
+    },
+    
+    // ตัวเลือกประเภทผู้ให้บริการ
+    operatorTypes: {
+      chinamobile: 'ไชน่าโมบาย',
+      chinaunicom: 'ไชน่ายูนิคอม',
+      chinatelecom: 'ไชน่าเทเลคอม',
+      international: 'นานาชาติ',
+      foreign_local: 'ต่างประเทศท้องถิ่น'
+    },
+    
+    // ข้อความตรวจสอบแบบฟอร์ม
+    packageNameRequired: 'กรุณาป้อนชื่อแพ็กเกจ',
+    operatorTypeRequired: 'กรุณาเลือกประเภทผู้ให้บริการ',
+    statusRequired: 'กรุณาเลือกสถานะ',
+    packageSizeRequired: 'กรุณาป้อนขนาดแพ็กเกจ',
+    standardPriceRequired: 'กรุณาป้อนราคามาตรฐาน'
+  };

+ 63 - 0
src/i18n/th/form.js

@@ -0,0 +1,63 @@
+export default {
+  "Key": "id",
+  "Id": "รหัส",
+  "Delete": "ลบ",
+  "DeleteConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบข้อมูลนี้?",
+  "Edit": "แก้ไข",
+  "Add": "เพิ่ม",
+  "Cancel": "ยกเลิก",
+  "Confirm": "ยืนยัน",
+  "Search": "ค้นหา",
+  "Reset": "รีเซ็ต",
+  "Export": "ส่งออก",
+  "Import": "นำเข้า",
+  "Status": "สถานะ",
+  "ExportSuccess": "ส่งออกสำเร็จ",
+  "ImportSuccess": "นำเข้าสำเร็จ",
+  "ExportFailed": "ส่งออกล้มเหลว",
+  "ImportFailed": "นำเข้าล้มเหลว",
+  "ExportConfirm": "คุณแน่ใจหรือไม่ว่าต้องการส่งออก?",
+  "ImportConfirm": "คุณแน่ใจหรือไม่ว่าต้องการนำเข้า?",
+  "PleaseEnterThe": "กรุณากรอก",
+
+  "DBdistributorSource": "แหล่งที่มา DB",
+
+  // Menu form
+  "Name": "ชื่อเมนู",
+  "Icon": "ไอคอน",
+  "SortNumber": "หมายเลขเรียงลำดับ",
+  "Refresh": "รีเฟรช",
+  "Path": "ตัวระบุ",
+  "Url": "เส้นทาง",
+
+  // Dictionary
+  "dictionaryName": "ชื่อพจนานุกรม",
+  "dictionaryType": "ประเภทพจนานุกรม",
+  "dictionaryValue": "ค่า",
+  "dictionarySortNumber": "หมายเลขเรียงลำดับ",
+  "dictionaryDescription": "คำอธิบาย",
+  "createUser": "สร้างโดย",
+  "updateUser": "อัปเดตโดย",
+  "createTime": "เวลาที่สร้าง",
+  "updateTime": "เวลาที่อัปเดต",
+
+  // Distributor
+  "distributorName": "ชื่อผู้จัดจำหน่าย",
+  "distributorContact": "ผู้ติดต่อ",
+  "distributorPhone": "เบอร์โทรติดต่อ",
+
+  // Data pool form
+  "datapoolForm": {
+    "title": "สร้างพูล SIM การ์ด",
+    "dealer": "ตัวแทนจำหน่าย",
+    "poolName": "ชื่อพูล",
+    "operator": "ผู้ให้บริการ",
+    "planType": "ประเภทแผน",
+    "displayFlow": "แสดงการไหล",
+    "plan": "แผน",
+    "pleaseSelect": "กรุณาเลือก",
+    "pleaseEnter": "กรุณากรอก",
+    "submit": "ส่ง",
+    "cancel": "ยกเลิก"
+  },
+}

+ 11 - 0
src/i18n/th/global.js

@@ -0,0 +1,11 @@
+export default {
+  "logOut": "เลิก",
+  newMenu: "เมนูใหม่",
+  common: {
+    search: 'ค้นหา',
+    reset: 'รีเซ็ต',
+    edit: 'แก้ไข',
+    delete: 'ลบ',
+    operations: 'การดำเนินการ'
+  }
+}

+ 15 - 0
src/i18n/th/index.js

@@ -0,0 +1,15 @@
+import login from './login'
+import global from './global'
+import form from './form'
+import supplier from './supplier'
+import plan from './plan'
+import dataPackage from './dataPackage'
+
+export default {
+  login,
+  global,
+  form,
+  supplier,
+  plan,
+  dataPackage
+}

+ 3 - 0
src/i18n/th/login.js

@@ -0,0 +1,3 @@
+export default {
+  'Welcome': 'ยินดีต้อนรับสู่เข้าสู่ระบบ',
+}

+ 74 - 0
src/i18n/th/plan.js

@@ -0,0 +1,74 @@
+export default {
+    feeName: 'ชื่อค่าบริการ',
+    enterFeeName: 'กรุณาป้อนชื่อหรือรหัสค่าบริการ',
+    operatorType: 'ประเภทผู้ให้บริการ',
+    selectOperatorType: 'กรุณาเลือก',
+    addFee: 'เพิ่มค่าบริการ',
+    editFee: 'แก้ไขค่าบริการ',
+    id: 'ลำดับ',
+    feeCode: 'รหัสค่าบริการ',
+    supplierName: 'ชื่อผู้จัดจำหน่าย',
+    dataType: 'ประเภทข้อมูล',
+    billingCategory: 'หมวดหมู่การเรียกเก็บเงิน',
+    billingCycle: 'รอบการเรียกเก็บเงิน',
+    packageSize: 'ขนาดแพ็คเกจ',
+    standardPrice: 'ราคามาตรฐาน',
+    cycleLimitation: 'ข้อจำกัดรอบ',
+    status: {
+      normal: 'ปกติ',
+      disabled: 'ปิดใช้งาน',
+      offline: 'ออฟไลน์'
+    },
+    operatorTypes: {
+      general: 'ทั่วไป',
+      specific: 'เฉพาะเจาะจง'
+    },
+    searchExecuted: 'ดำเนินการค้นหาแล้ว',
+    feeDeleted: 'ลบค่าบริการ {name} แล้ว',
+    feeUpdated: 'อัปเดตค่าบริการ {name} แล้ว',
+    feeAdded: 'เพิ่มค่าบริการใหม่ {name} แล้ว',
+    basicInfo: 'ข้อมูลพื้นฐาน',
+    supplier: 'ผู้ให้บริการ',
+    selectSupplier: 'กรุณาเลือกผู้ให้บริการ',
+    supplier1: 'ผู้จัดจำหน่าย 1',
+    supplier2: 'ผู้จัดจำหน่าย 2',
+    general: 'ทั่วไป',
+    specific: 'เฉพาะเจาะจง',
+    data: 'ข้อมูล',
+    voice: 'เสียง',
+    nb: 'NB',
+    sms: 'SMS',
+    billingInfo: 'ข้อมูลการเรียกเก็บเงิน',
+    daily: 'รายวัน',
+    monthly: 'รายเดือน',
+    enterPackageSize: 'กรุณาป้อนขนาดแพ็คเกจ',
+    enterPrice: 'กรุณาป้อนราคา',
+    currency: 'บาท',
+    billingCycleType: 'ประเภทรอบการเรียกเก็บเงิน',
+    month: 'เดือน',
+    subscriptionPeriod: 'ระยะเวลาการสมัคร',
+    minimum: 'ต่ำสุด',
+    maximum: 'สูงสุด',
+    monthsMin: 'เดือน (ต่ำสุด)',
+    monthsMax: 'เดือน (สูงสุด)',
+    overagePrice: 'ราคาส่วนเกิน',
+    enterOveragePrice: 'กรุณาป้อนราคาส่วนเกิน',
+    connectionTimes: 'จำนวนการเชื่อมต่อ (ครั้ง)',
+    times: 'ครั้ง',
+    voiceRate: 'อัตราค่าโทรเข้า',
+    minute: 'นาที',
+    billingCode: 'รหัสการเรียกเก็บเงิน',
+    autoGenerated: 'สร้างอัตโนมัติ',
+    feeCodeRequired: 'กรุณาป้อนรหัสค่าบริการ',
+    feeNameRequired: 'กรุณาป้อนชื่อค่าบริการ',
+    supplierRequired: 'กรุณาเลือกผู้ให้บริการ',
+    dataTypeRequired: 'กรุณาเลือกประเภทข้อมูล',
+    billingCategoryRequired: 'กรุณาเลือกหมวดหมู่การเรียกเก็บเงิน',
+    statusRequired: 'กรุณาเลือกสถานะ',
+    billingCycleRequired: 'กรุณาเลือกรอบการเรียกเก็บเงิน',
+    packageSizeRequired: 'กรุณาป้อนขนาดแพ็คเกจ',
+    standardPriceRequired: 'กรุณาป้อนราคามาตรฐาน',
+    minPeriodRequired: 'กรุณาป้อนระยะเวลาการสมัครขั้นต่ำ',
+    maxPeriodRequired: 'กรุณาป้อนระยะเวลาการสมัครสูงสุด',
+    fillRequiredFields: 'กรุณากรอกข้อมูลในช่องที่จำเป็นทั้งหมด'
+  }

+ 85 - 0
src/i18n/th/supplier.js

@@ -0,0 +1,85 @@
+export default {
+    addSupplier: 'เพิ่มซัพพลายเออร์',
+    editSupplier: 'แก้ไขซัพพลายเออร์',
+    supplierName: 'ชื่อซัพพลายเออร์',
+    enterSupplierName: 'กรุณาป้อนชื่อซัพพลายเออร์',
+    operatorType: 'ประเภทผู้ให้บริการ',
+    selectOperatorType: 'กรุณาเลือกประเภทผู้ให้บริการ',
+    id: 'รหัส',
+    operatorCode: 'รหัสผู้ให้บริการ',
+    operatorName: 'ชื่อผู้ให้บริการ',
+    updateTime: 'เวลาอัปเดต',
+    searchExecuted: 'ดำเนินการค้นหาแล้ว',
+    searchReset: 'รีเซ็ตเงื่อนไขการค้นหาแล้ว',
+    operatorTypes: {
+      foreign_local: 'ต่างประเทศท้องถิ่น',
+      international: 'นานาชาติ',
+      chinamobile: 'ไชน่าโมบาย',
+      chinaunicom: 'ไชน่ายูนิคอม',
+      chinatelecom: 'ไชน่าเทเลคอม'
+    },
+    basicInfo: 'ข้อมูลพื้นฐาน',
+    operatorApiConfig: 'การกำหนดค่า API ของผู้ให้บริการ',
+    advancedConfig: 'การกำหนดค่าขั้นสูง',
+    apiTemplate: 'เทมเพลต API',
+    selectApiTemplate: 'เลือกเทมเพลต API',
+    apiTemplates: {
+      telecom_CMP: 'เทเลคอม CMP'
+    },
+    apiUrl: 'URL ของ API',
+    enterApiUrl: 'ป้อน URL ของ API',
+    realNameAuthUrl: 'URL สำหรับการยืนยันตัวตน',
+    enterRealNameAuthUrl: 'ป้อน URL สำหรับการยืนยันตัวตน',
+    realNameUrlParam: 'พารามิเตอร์ URL สำหรับการยืนยันตัวตน',
+    enterRealNameUrlParam: 'ป้อนพารามิเตอร์ URL สำหรับการยืนยันตัวตน',
+    appKey: 'คีย์แอป',
+    enterAppKey: 'ป้อนคีย์แอป',
+    apiSecret: 'รหัสลับ API',
+    enterApiSecret: 'ป้อนรหัสลับ API',
+    key: 'คีย์',
+    enterKey: 'ป้อนคีย์',
+    stopCardAction: 'การดำเนินการหยุดบัตร',
+    selectStopCardAction: 'เลือกการดำเนินการหยุดบัตร',
+    stopCardActions: {
+      suspend: 'ระงับ'
+    },
+    needManualRealName: 'ต้องการยืนยันตัวตนด้วยตนเอง',
+    need: 'ต้องการ',
+    noNeed: 'ไม่ต้องการ',
+    autoSyncUsage: 'ซิงค์การใช้งานอัตโนมัติ',
+    enable: 'เปิดใช้งาน',
+    disable: 'ปิดใช้งาน',
+    useOperatorApi: 'ใช้ API ของผู้ให้บริการ',
+    call: 'เรียกใช้',
+    noCall: 'ไม่เรียกใช้',
+    setQuantityLimit: 'ตั้งค่าจำกัดปริมาณ',
+    quantityLimit: 'จำกัดปริมาณ',
+    noQuantityLimit: 'ไม่จำกัดปริมาณ',
+    useOperatorDataPool: 'ใช้พูลข้อมูลของผู้ให้บริการ',
+    silentPeriod: 'ช่วงเวลาเงียบ',
+    enterSilentPeriod: 'ป้อนช่วงเวลาเงียบ',
+    unusedSuspensionDays: 'วันที่ระงับเมื่อไม่ได้ใช้งาน',
+    enterDays: 'ป้อนจำนวนวัน',
+    maxWhitelistAdditions: 'จำนวนครั้งสูงสุดในการเพิ่มรายการอนุญาต',
+    enterTimes: 'ป้อนจำนวนครั้ง',
+    totalActiveWhitelists: 'จำนวนรายการอนุญาตที่ใช้งานทั้งหมด',
+    enterCount: 'ป้อนจำนวน',
+    dailyWhitelistRemovals: 'จำนวนการลบรายการอนุญาตต่อวัน',
+    whitelistAddOrder: 'ลำดับการเพิ่มรายการอนุญาต',
+    selectOrder: 'เลือกลำดับ',
+    noWhitelist: 'ไม่มีรายการอนุญาต',
+    numberGenerationRule: 'กฎการสร้างหมายเลข',
+    enterNumberGenerationRule: 'ป้อนกฎการสร้างหมายเลข',
+    supplierNameRequired: 'ต้องระบุชื่อซัพพลายเออร์',
+    operatorTypeRequired: 'ต้องระบุประเภทผู้ให้บริการ',
+    apiTemplateRequired: 'ต้องระบุเทมเพลต API',
+    apiUrlRequired: 'ต้องระบุ URL ของ API',
+    appKeyRequired: 'ต้องระบุคีย์แอป',
+    apiSecretRequired: 'ต้องระบุรหัสลับ API',
+    needManualRealNameRequired: 'ต้องระบุความต้องการยืนยันตัวตนด้วยตนเอง',
+    autoSyncUsageRequired: 'ต้องระบุการซิงค์การใช้งานอัตโนมัติ',
+    useOperatorApiRequired: 'ต้องระบุการใช้ API ของผู้ให้บริการ',
+    useOperatorDataPoolRequired: 'ต้องระบุการใช้พูลข้อมูลของผู้ให้บริการ',
+    silentPeriodRequired: 'ต้องระบุช่วงเวลาเงียบ',
+    fillRequiredFields: 'กรุณากรอกข้อมูลในฟิลด์ที่จำเป็นทั้งหมด'
+  }

+ 208 - 0
src/i18n/zh/customer.js

@@ -0,0 +1,208 @@
+export default {
+  // 主列表页面
+  customerName: "客户名称",
+  enterCustomerName: "请输入客户名称",
+  operatorType: "运营商类型",
+  selectOperatorType: "请选择运营商类型",
+  addCustomer: "新增客户",
+  batchDelete: "批量删除",
+  searchExecuted: "已执行搜索",
+  searchReset: "搜索条件已重置",
+  batchDeleteWarning: "确定要批量删除选中的客户吗?",
+  statusName: "状态",
+
+  basicInfoSection: "基本信息",
+  smsInfoSection: "手机短信",
+  accountManagement: "账户资费管理",
+  customerData: "客户资料",
+  interfaceParams: "接口参数",
+  billingInfo: "发票信息",
+  purchaseQuality: "购卡资质",
+  warningNumbers: "预警参数",
+
+  // 表格列
+  id: "ID",
+  customerCode: "客户编码",
+  accountNumber: "账号",
+  accountBalance: "账户余额",
+  paymentMethod: "付费方式",
+  customerStatus: "客户状态",
+  validPeriod: "有效期",
+  activeCards: "有效卡数/总卡数",
+  updateTime: "更新时间",
+
+  // 操作按钮
+  recharge: "充值",
+  packageManagement: "套餐管理",
+  edit: "编辑",
+
+  // 状态
+  status: {
+    normal: "正常",
+    disabled: "禁用",
+    pending: "待审",
+    suspended: "暂停",
+  },
+
+  // 运营商类型
+  operatorTypes: {
+    domestic: "国内",
+    international: "国际",
+  },
+
+  // 新增/编辑客户对话框
+  editCustomer: "编辑客户",
+  basicInfo: "基本信息",
+  smsInfo: "手机短信",
+  enterCustomerCode: "请输入客户编码",
+  enterAccountNumber: "请输入账号",
+  password: "登录密码",
+  enterPassword: "请输入登录密码",
+  generatePassword: "重置密码",
+  remark: "号码备注",
+  enterRemark: "请输入号码备注",
+  selectValidPeriod: "请选择有效期",
+
+  smsNumber: "短信子号",
+  enterSmsNumber: "请输入短信子号",
+  smsName: "短信签名",
+  enterSmsName: "请输入短信签名",
+  loginSmsTemplate: "登录短信模板",
+  enterLoginSmsTemplate: "请输入登录短信模板",
+  alarmSmsTemplate: "预警短信模板",
+  enterAlarmSmsTemplate: "请输入预警短信模板",
+
+  // 表单验证消息
+  customerCodeRequired: "请输入客户编码",
+  customerNameRequired: "请输入客户名称",
+  accountNumberRequired: "请输入账号",
+  passwordRequired: "请输入登录密码",
+  validPeriodRequired: "请选择有效期",
+  statusRequired: "请选择客户状态",
+
+  // 其他
+  passwordGenerated: "密码已生成",
+
+  // 账户资费管理相关翻译
+  accountBalance: '账户余额',
+  rechargeLimit: '充值限制',
+  creditLimit: '信用额度',
+  mobileSmsPrice: '手机短信单价',
+  iotSmsPrice: '物联网短信单价',
+  paymentMethod: '付费方式',
+  currency: '元',
+  currencyPerTime: '元/次',
+  currencyPerMessage: '元/条',
+  paymentMethodName: '付费方式名称',
+  paymentMethod: {
+    prepaid: '预付费',
+    postpaid: '后付费'
+  },
+
+  // 客户资料相关翻译
+  platformName: '平台名称',
+  enterPlatformName: '请输入平台名称',
+  companyName: '公司名称',
+  enterCompanyName: '请输入公司名称',
+  servicePhone: '服务电话',
+  enterServicePhone: '请输入服务电话',
+  domain: '域名',
+  enterDomain: '请输入域名',
+  websiteBackend: '网站备案号',
+  enterWebsiteBackend: '请输入网站备案号',
+  customerAddress: '客户地址',
+  enterCustomerAddress: '请输入客户地址',
+  
+  contactPerson: '联系人',
+  enterContactPerson: '请输入联系人',
+  contactPhone: '联系人手机号',
+  enterContactPhone: '请输入联系人手机号',
+  contactEmail: '联系人邮箱',
+  enterContactEmail: '请输入联系人邮箱',
+  contactQQ: '联系人QQ号',
+  enterContactQQ: '请输入联系人QQ号',
+  
+  billingPerson: '收件人',
+  enterBillingPerson: '请输入收件人',
+  billingPhone: '收件人手机号',
+  enterBillingPhone: '请输入收件人手机号',
+  billingAddress: '收件人地址',
+  enterBillingAddress: '请输入收件人地址',
+
+  // 接口参数相关翻译
+  smsStatusReportUrl: '短信状态报告推送地址',
+  enterSmsStatusReportUrl: '请输入短信状态报告推送地址',
+  reportIp: '报备IP',
+  enterReportIp: '请输入报备IP',
+  deliveryPriority: '推送优先级',
+  selectDeliveryPriority: '请选择推送优先级',
+  deliveryPriorityName: '推送优先级',
+  deliveryPriority: {
+    high: '高',
+    medium: '中',
+    low: '低'
+  },
+  dataReportUrl: '数据推送地址',
+  enterDataReportUrl: '请输入数据推送地址',
+
+  // 发票信息相关翻译
+  invoiceTitle: '发票抬头',
+  enterInvoiceTitle: '请输入发票抬头',
+  taxRegistrationNumber: '税务登记证号',
+  enterTaxRegistrationNumber: '请输入税务登记证号',
+  registeredAddress: '注册场所地址',
+  enterRegisteredAddress: '请输入注册场所地址',
+  registeredPhone: '注册固定电话',
+  enterRegisteredPhone: '请输入注册固定电话',
+  bankName: '开户银行名称',
+  enterBankName: '请输入开户银行名称',
+  bankAccountName: '账户名称',
+  enterBankAccountName: '请输入账户名称',
+  bankAccountNumber: '开户账号',
+  enterBankAccountNumber: '请输入开户账号',
+  taxIdentificationNumber: '纳税人识别号',
+  enterTaxIdentificationNumber: '请输入纳税人识别号',
+  invoiceRecipient: '收票人',
+  enterInvoiceRecipient: '请输入收票人',
+  recipientPhone: '收票人手机号',
+  enterRecipientPhone: '请输入收票人手机号',
+  invoiceAddress: '收票地址',
+  enterInvoiceAddress: '请输入收票地址',
+  invoiceEmail: '收票邮箱',
+  enterInvoiceEmail: '请输入收票邮箱',
+  businessLicenseFile: '营业执照扫描件(加盖公章)',
+  taxRegistrationFile: '税务登记复印件',
+  generalTaxpayerFile: '一般纳税人认定表复印件',
+  uploadFile: '点击上传图片',
+
+  // 购卡资质相关翻译
+  businessLicenseFile: '营业执照扫描件(加盖公章)',
+  legalPersonIdCardFrontFile: '法人身份证人像面(加盖公章)',
+  legalPersonIdCardBackFile: '法人身份证国徽面(加盖公章)',
+  equipmentPurchaseFile: '设备照片(加盖公章)',
+  businessContractFile: '业务合同(加盖公章)',
+  informationSecurityFile: '信息安全承诺书(加盖公章)',
+  customerInformationFile: '客户信息表(加盖公章)',
+  obtainMethod: '获客方式及文字话术',
+  telephoneApplicationForm: '电话业务用户申请登记表',
+  otherDocuments: '其他附件',
+  uploadFile: '点击上传图片',
+  imageUploadDescription: '1.建议上传尺寸为360*240px的图片\n2.图片仅支持jpg或png格式\n3.图片不得大于2M',
+  obtainMethodDescription: '支持扩展名:.rar .zip .doc .docx .ppt .pptx .xls .xlsx .pdf .jpg .jpeg .png .txt\n最多上传10个文件,每个文件不超过2M',
+  telephoneApplicationFormDescription: '支持扩展名:.rar .zip .doc .docx .ppt .pptx .xls .xlsx .pdf .jpg .jpeg .png .txt\n最多上传10个文件,每个文件不超过2M',
+  otherDocumentsDescription: '支持扩展名:.rar .zip .doc .docx .ppt .pptx .xls .xlsx .pdf .jpg .jpeg .png .txt\n最多上传10个文件,每个文件不超过2M',
+
+   // 预警参数相关翻译
+   warningNumbers: '预警参数',
+   accountBalanceWarning: '账号余额预警',
+   reachWarning: '达量预警',
+   stopSending: '达量停机',
+   networkDisconnection: '达量断网',
+   warningPhones: '预警手机',
+   warningEmails: '预警邮箱',
+   enterWarningPhones: '多个逗号隔开,最多3个',
+   enterWarningEmails: '多个逗号隔开,最多3个',
+   noRecovery: '不复机',
+   manualRecovery: '手工恢复',
+   autoRecovery: '自动恢复',
+};

+ 22 - 0
src/i18n/zh/customerPackage.js

@@ -0,0 +1,22 @@
+export default {
+    title: '客户套餐管理',
+    mainPackage: '主套餐',
+    packageCode: '资费',
+    enterPackageCode: '请输入资费名称或代码',
+    packageType: '运营商类型',
+    selectPackageType: '请选择',
+    regular: '常规',
+    custom: '自定义',
+    addPackage: '新增',
+    serialNumber: '序号',
+    packageName: '资费名称',
+    providerName: '供应商名称',
+    billingType: '计费分类',
+    billingPeriod: '计费周期',
+    packageSize: '资费包大小',
+    standardPrice: '标准价格',
+    setPrice: '设置价格',
+    addPackageInfo: '添加新套餐功能尚未实现',
+    setPriceFor: '设置价格for',
+    deleteConfirm: '确定要删除',
+  };

+ 56 - 0
src/i18n/zh/dataPackage.js

@@ -0,0 +1,56 @@
+export default {
+    // 数据包管理
+    addDataPackage: '添加数据包',
+    editDataPackage: '编辑数据包',
+    packageName: '套餐名称',
+    enterPackageName: '请输入套餐名称',
+    operatorType: '运营商类型',
+    selectOperatorType: '请选择运营商类型',
+    searchExecuted: '搜索已执行',
+    searchReset: '搜索条件已重置',
+    selectStatus: '请选择状态',
+    statusName: '状态',
+    
+    // 表格列
+    id: '序号',
+    packageCode: '套餐编码',
+    operatorName: '运营商名称',
+    packageSize: '套餐大小',
+    standardPrice: '标准价格',
+    status: '状态',
+    
+    // 操作提示
+    packageDeleted: '数据包 {name} 已删除',
+    packageUpdated: '数据包 {name} 已更新',
+    packageAdded: '数据包 {name} 已添加',
+    
+    // 表单字段
+    operatorPackageCode: '运营商套餐编码',
+    enterOperatorPackageCode: '请输入运营商套餐编码',
+    enterPackageSize: '请输入套餐大小',
+    enterStandardPrice: '请输入标准价格',
+    selectStatus: '请选择状态',
+    
+    // 状态选项
+    status: {
+      normal: '正常',
+      disabled: '停用',
+      offline: '下线'
+    },
+    
+    // 运营商类型选项
+    operatorTypes: {
+      chinamobile: '中国移动',
+      chinaunicom: '中国联通',
+      chinatelecom: '中国电信',
+      international: '国际通用',
+      foreign_local: '境外本地'
+    },
+    
+    // 表单验证消息
+    packageNameRequired: '请输入套餐名称',
+    operatorTypeRequired: '请选择运营商类型',
+    statusRequired: '请选择状态',
+    packageSizeRequired: '请输入套餐大小',
+    standardPriceRequired: '请输入标准价格'
+  };

+ 86 - 0
src/i18n/zh/form.js

@@ -0,0 +1,86 @@
+export default {
+  "Key": "id",
+  "Id": "操作",
+  "Delete": "删除",
+  "DeleteConfirm": "确认删除该信息?",
+  "Edit": "编辑",
+  "Add": "添加",
+  "Cancel": "取消",
+  "Confirm": "确定",
+  "Search": "搜索",
+  "Reset": "重置",
+  "Export": "导出",
+  "Import": "导入",
+  "Status": "状态",
+  "ExportSuccess": "导出成功",
+  "ImportSuccess": "导入成功",
+  "ExportFailed": "导出失败",
+  "ImportFailed": "导入失败",
+  "ExportConfirm": "确定要导出吗?",
+  "ImportConfirm": "确定要导入吗?",
+  "ExportConfirm": "确定要导出吗?",
+  "PleaseEnterThe": "请输入",
+
+  "DBdistributorSource": "DB来源",
+
+
+  // 菜单表单
+  "Name": "菜单名称",
+  "Icon": "图标",
+  "SortNumber": "序号",
+  "Refresh": "缓存",
+  "Path": "标识",
+  "Url": "路由",
+
+  //字典
+  dictionaryName: "字典名称",
+  dictionaryType: "字典类型",
+  dictionaryValue: "值",
+  dictionarySortNumber: "序号",
+  dictionaryDescription: "描述",
+  createUser: "创建人",
+  updateUser: "更新人",
+  createTime: "创建时间",
+  updateTime: "更新时间",
+
+  // 分销商
+  distributorName: "分销商名称",
+  //联系人
+  distributorContact: "联系人",
+  distributorPhone: "联系电话",
+
+  // 数据池表单
+  datapoolForm: {
+    title: "创建机卡池",
+    dealer: "经销商",
+    poolName: "池名称",
+    operator: "运营商",
+    planType: "套餐类型",
+    displayFlow: "显示流量",
+    plan: "套餐",
+    pleaseSelect: "请选择",
+    pleaseEnter: "请输入",
+    submit: "提交",
+    cancel: "取消"
+  },
+
+  // 新卡表单
+  cardForm: {
+    addTitle: "添加新卡",
+    editTitle: "编辑卡片",
+    title: "添加新卡",
+    iccid: "ICCID",
+    cardNumber: "卡号",
+    category: "分类",
+    imei: "IMEI",
+    plan: "套餐",
+    riskGroup: "风控组",
+    dataUsage: "流量用量",
+    cardStatus: "卡状态",
+    operator: "运营商",
+    distributor: "经销商",
+    expirationDate: "到期时间",
+    pleaseEnter: "请输入",
+    pleaseSelect: "请选择",
+  },
+}

+ 12 - 0
src/i18n/zh/global.js

@@ -0,0 +1,12 @@
+
+export default {
+  "logOut": "退出登录",
+  "newMenu": "新增菜单",
+  common: {
+    search: '搜索',
+    reset: '重置',
+    edit: '编辑',
+    delete: '删除',
+    operations: '操作'
+  }
+}

+ 19 - 0
src/i18n/zh/index.js

@@ -0,0 +1,19 @@
+import login from './login'
+import global from './global'
+import form from './form'
+import supplier from './supplier'
+import plan from './plan'
+import dataPackage from './dataPackage'
+import customer from './customer'
+import customerPackage from './customerPackage'
+
+export default {
+  login,
+  global,
+  form,
+  supplier,
+  plan,
+  dataPackage,
+  customer,
+  customerPackage
+}

+ 3 - 0
src/i18n/zh/login.js

@@ -0,0 +1,3 @@
+export default {
+  'Welcome': '欢迎登录',
+}

+ 75 - 0
src/i18n/zh/plan.js

@@ -0,0 +1,75 @@
+
+export default {
+    feeName: '资费名称',
+    enterFeeName: '请输入资费名称或编码',
+    operatorType: '运营商类型',
+    selectOperatorType: '请选择',
+    addFee: '新增资费',
+    editFee: '编辑资费',
+    id: '序号',
+    feeCode: '资费编码',
+    supplierName: '供应商名称',
+    dataType: '流量类型',
+    billingCategory: '计费分类',
+    billingCycle: '计费周期',
+    packageSize: '资费包大小',
+    standardPrice: '标准价格',
+    cycleLimitation: '周期限制',
+    status: {
+      normal: '正常',
+      disabled: '禁用',
+      offline: '下架'
+    },
+    operatorTypes: {
+      general: '通用',
+      specific: '定向'
+    },
+    searchExecuted: '执行搜索操作',
+    feeDeleted: '已删除资费 {name}',
+    feeUpdated: '已更新资费 {name}',
+    feeAdded: '已添加新资费 {name}',
+    basicInfo: '基本信息',
+    supplier: '运营商',
+    selectSupplier: '请选择运营商',
+    supplier1: '供应商1',
+    supplier2: '供应商2',
+    general: '通用',
+    specific: '定向',
+    data: '流量',
+    voice: '语音',
+    nb: 'NB',
+    sms: '短信',
+    billingInfo: '计费信息',
+    daily: '按天',
+    monthly: '按月',
+    enterPackageSize: '请输入资费包大小',
+    enterPrice: '请输入价格',
+    currency: '元',
+    billingCycleType: '计费周期类型',
+    month: '月',
+    subscriptionPeriod: '订购周期',
+    minimum: '最短',
+    maximum: '最长',
+    monthsMin: '个月,最短',
+    monthsMax: '个月',
+    overagePrice: '超套价格',
+    enterOveragePrice: '请输入超套价格',
+    connectionTimes: '连接数(次)',
+    times: '次',
+    voiceRate: '语音被叫',
+    minute: '分钟',
+    billingCode: '计费函数',
+    autoGenerated: '自动生成',
+    feeCodeRequired: '请输入资费编码',
+    feeNameRequired: '请输入资费名称',
+    supplierRequired: '请选择运营商',
+    dataTypeRequired: '请选择流量类型',
+    billingCategoryRequired: '请选择计费分类',
+    statusRequired: '请选择状态',
+    billingCycleRequired: '请选择计费周期',
+    packageSizeRequired: '请输入资费包大小',
+    standardPriceRequired: '请输入标准价格',
+    minPeriodRequired: '请输入最短订购周期',
+    maxPeriodRequired: '请输入最长订购周期',
+    fillRequiredFields: '请填写所有必填字段'
+  }

+ 85 - 0
src/i18n/zh/supplier.js

@@ -0,0 +1,85 @@
+export default {
+    addSupplier: '新增供应商',
+    editSupplier: '编辑供应商',
+    supplierName: '供应商名称',
+    enterSupplierName: '请输入供应商名称',
+    operatorType: '运营商类型',
+    selectOperatorType: '请选择运营商类型',
+    id: '序号',
+    operatorCode: '运营商编码',
+    operatorName: '运营商名称',
+    updateTime: '更新时间',
+    searchExecuted: '搜索已执行',
+    searchReset: '搜索条件已重置',
+    operatorTypes: {
+      foreign_local: '境外本地',
+      international: '国际通用',
+      chinamobile: '中国移动',
+      chinaunicom: '中国联通',
+      chinatelecom: '中国电信'
+    },
+    basicInfo: '基本信息',
+    operatorApiConfig: '运营商API配置',
+    advancedConfig: '高级配置',
+    apiTemplate: 'API模板',
+    selectApiTemplate: '选择API模板',
+    apiTemplates: {
+      telecom_CMP: '电信CMP'
+    },
+    apiUrl: 'API地址',
+    enterApiUrl: '输入API地址',
+    realNameAuthUrl: '实名认证地址',
+    enterRealNameAuthUrl: '输入实名认证地址',
+    realNameUrlParam: '实名URL参数',
+    enterRealNameUrlParam: '输入实名URL参数',
+    appKey: '应用密钥',
+    enterAppKey: '输入应用密钥',
+    apiSecret: 'API密钥',
+    enterApiSecret: '输入API密钥',
+    key: '密钥',
+    enterKey: '输入密钥',
+    stopCardAction: '停卡动作',
+    selectStopCardAction: '选择停卡动作',
+    stopCardActions: {
+      suspend: '暂停'
+    },
+    needManualRealName: '需要手动实名',
+    need: '需要',
+    noNeed: '不需要',
+    autoSyncUsage: '自动同步用量',
+    enable: '启用',
+    disable: '禁用',
+    useOperatorApi: '使用运营商API',
+    call: '调用',
+    noCall: '不调用',
+    setQuantityLimit: '设置用量限制',
+    quantityLimit: '达量断网',
+    noQuantityLimit: '不达量断网',
+    useOperatorDataPool: '使用运营商数据池',
+    silentPeriod: '静默期',
+    enterSilentPeriod: '输入静默期',
+    unusedSuspensionDays: '未使用停机天数',
+    enterDays: '输入天数',
+    maxWhitelistAdditions: '最大白名单添加次数',
+    enterTimes: '输入次数',
+    totalActiveWhitelists: '总活跃白名单数',
+    enterCount: '输入数量',
+    dailyWhitelistRemovals: '每日白名单移除数',
+    whitelistAddOrder: '白名单添加顺序',
+    selectOrder: '选择顺序',
+    noWhitelist: '无白名单',
+    numberGenerationRule: '号码生成规则',
+    enterNumberGenerationRule: '输入号码生成规则',
+    supplierNameRequired: '供应商名称为必填项',
+    operatorTypeRequired: '运营商类型为必填项',
+    apiTemplateRequired: 'API模板为必填项',
+    apiUrlRequired: 'API地址为必填项',
+    appKeyRequired: '应用密钥为必填项',
+    apiSecretRequired: 'API密钥为必填项',
+    needManualRealNameRequired: '需要手动实名为必填项',
+    autoSyncUsageRequired: '自动同步用量为必填项',
+    useOperatorApiRequired: '使用运营商API为必填项',
+    useOperatorDataPoolRequired: '使用运营商数据池为必填项',
+    silentPeriodRequired: '静默期为必填项',
+    fillRequiredFields: '请填写所有必填字段'
+  }

+ 49 - 0
src/main.js

@@ -0,0 +1,49 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router, { setupRouter } from '@/router'
+import { i18n } from '@/i18n'
+import { setupStore } from '@/store'
+
+
+import ArcoVue from '@arco-design/web-vue';
+import '@arco-design/web-vue/dist/arco.css';
+
+
+/* 其他模块引入 */
+import 'virtual:svg-icons-register'
+// 引入 Symbol icon图标
+import '@/assets/iconsvg/iconfont'
+import SvgIcon from '@/components/Svg-icon/SvgIcon.vue'
+// 额外引入图标库
+import ArcoVueIcon from '@arco-design/web-vue/es/icon'
+
+async function appInit() {
+  const app = createApp(App)
+  // 注入组件
+  app.component('SvgIcon', SvgIcon)
+  // 挂载状态管理
+  await setupStore(app)
+  // arco-design
+  app.use(ArcoVue)
+  app.use(ArcoVueIcon)
+  app.use(i18n)
+  
+  window['$t'] = i18n.global.t
+
+  // 挂载路由
+  await setupRouter(app)
+  // 路由准备就绪后挂载APP实例
+  await router.isReady()
+  // 挂载到页面
+  app.mount('#app', true)
+
+  // 挂载到 window
+  window['$vue'] = app
+}
+
+appInit().then(() => {
+  // 捕获全局错误
+  // window.addEventListener("unhandledrejection", event => {
+  //   console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
+  // });
+})

+ 23 - 0
src/router/index.js

@@ -0,0 +1,23 @@
+import { pinia } from '@/store'
+import { createRouter, createWebHistory } from 'vue-router'
+import { createRouterGuards } from './router.guards.js'
+import { useSystemStore } from '@/store/modules/systemStore'
+import { systemRoutes } from './router.system.js'
+import { updateRouteByMenu  } from "./router.update.js"
+
+const systemStore = useSystemStore(pinia)
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes: systemRoutes
+});
+
+
+export async function setupRouter(app) {
+  await updateRouteByMenu(router, systemStore)
+  app.use(router)
+  // 创建路由守卫
+  createRouterGuards(router, systemStore)
+}
+
+export default router;

+ 37 - 0
src/router/router.guards.js

@@ -0,0 +1,37 @@
+import { fn_logout } from '@/utils'
+
+const paths  = ['/login']
+
+export function createRouterGuards(router, systemStore) {
+  const routes = router.getRoutes()
+
+  // 前置
+  router.beforeEach(async (to, from, next) => {
+    // 如果用户进入了登录页面就相当于退出
+    if (paths.includes(to.path)) {
+    
+      fn_logout()
+      return next()
+    }
+
+    // 验证菜单
+    const menus = systemStore.getMenus
+    if(menus.length == 0) return next('/login')
+    if (to.matched.length === 0) {
+      return  next('/errors')
+    }
+  
+    next()
+  })
+
+  router.afterEach((to, _, next) => {
+    // const Loading = window['$loading']
+    // setTitle(to.meta.title as string | undefined)
+    // Loading && Loading.submit-success()
+  })
+
+  // 错误
+  router.onError(error => {
+    console.log(error, '路由错误')
+  })
+}

+ 69 - 0
src/router/router.system.js

@@ -0,0 +1,69 @@
+
+// 异步模块
+// const LAYOUT = () => import('@/components/Layout/Layout.vue')
+const ERROR = () => import('@/views/exception/errors.vue')
+
+// login
+const LOGIN = () => import("@/views/login/index.vue")
+
+
+export const systemRoutes = [
+  {
+    path: "/login",
+    name: "login",
+    component: LOGIN,
+    meta: {
+      title: '登录'
+    }
+  },
+  {
+    path: '/errors',
+    name: 'errors',
+    meta: {
+      title: '异常页',
+    },
+    component: ERROR,
+  }
+];
+
+// const systemRouter = [
+//   {
+//     path: '/system',
+//     name: 'system',
+//     redirect: '/system-user',
+//     meta: {
+//       icon: "menu-gongju",
+//       title: '系统',
+//     },
+//     children: [
+//       {
+//         path: '/system-user',
+//         name: 'system-user',
+//         meta: {
+//           title: '员工账号',
+//         },
+//         component: () => import('@/views/system/user/index.vue')
+//       },
+//       {
+//         path: '/system-role',
+//         name: 'system-role',
+//         meta: {
+//           title: '角色权限',
+//         },
+//         component: () => import('@/views/system/role/index.vue')
+//       },
+//       {
+//         path: '/system-menu',
+//         name: 'system-menu',
+//         meta: {
+//           title: '菜单管理',
+//         },
+//         component: () => import('@/views/system/menu/index.vue')
+//       }
+//     ]
+//   }
+// ]
+
+
+
+// export default systemRouter

+ 70 - 0
src/router/router.update.js

@@ -0,0 +1,70 @@
+import { systemUserInfoMenu } from "@/api/path/system.api";
+import { systemRoutes } from "./router.system";
+import { getLocalStorage, getParseLang } from '@/utils'
+import { lang } from '@/settings/designSetting'
+
+const LAYOUT = () => import("@/components/Layout/Layout.vue");
+
+const findPath = (data) => {
+  if (data.children && data.children.length > 0) {
+    return findPath(data.children[0]);
+  } else {
+    return data;
+  }
+};
+
+// 生成路由
+const comp = import.meta.glob("../views/**/index.vue");
+const component = (view) => {
+  return () => comp[`../${view}`]();
+};
+
+ 
+
+// 路由格式转化
+const toRoutesJson = (data) => {
+  const LANG =  getLocalStorage("LANG") || {lang}
+  return data.map((item) => {
+    const routeItem = {
+      path: `/${item.path}`,
+      name: item.path,
+      meta: {
+        title: getParseLang(item.name, LANG.lang),
+        icon: item.icon,
+      },
+    };
+    if (item.url && item.type == "2") {
+      routeItem.component = component(item.url);
+    }
+    if (item.children && item.children.length > 0) {
+      routeItem.children = toRoutesJson(item.children);
+    }
+    return routeItem;
+  });
+};
+
+export const updateRouteByMenu = async (router, systemStore) => {
+  if(!systemStore.getToken) {
+    router.push({
+      name: "login",
+    })
+    return
+  }
+  const { data: menuList } = await systemUserInfoMenu();
+  systemStore.setStateValue({
+    key: "menus",
+    value: menuList || [],
+    localStorage: true,
+  });
+  const pathHome = findPath(menuList[0]);
+  const mainRoutes = toRoutesJson(menuList);
+  const routes = {
+    path: "/",
+    name: "main",
+    redirect: pathHome.path,
+    component: LAYOUT,
+    children: mainRoutes,
+  };
+  router.options.routes = [routes, ...systemRoutes];
+  await router.addRoute(routes);
+};

+ 10 - 0
src/settings/designSetting.js

@@ -0,0 +1,10 @@
+// 默认语言
+export const lang = "zh-CN" 
+
+// 主题配置
+export const theme = {
+  // 默认是否开启深色主题
+  darkTheme: true,
+  //默认主题色
+  appTheme: '#1890ff'
+}

+ 11 - 0
src/settings/pagination.js

@@ -0,0 +1,11 @@
+export const getInitPagination =  {
+  size: 'small',
+  total: 0,
+  pageSize: 10,
+  current: 1,
+  showPageSize: true,
+  showJumper: true,
+  hideOnSinglePage: false,
+  pageSizeOptions: [5, 10, 20, 30, 40],
+  showTotal: true,
+}

+ 9 - 0
src/store/index.js

@@ -0,0 +1,9 @@
+import { createPinia } from 'pinia';
+
+const pinia = createPinia();
+
+export function setupStore(app) {
+  app.use(pinia);
+}
+
+export { pinia };

+ 29 - 0
src/store/modules/designStore.js

@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia'
+import { theme } from '@/settings/designSetting'
+import { setLocalStorage, getLocalStorage } from '@/utils'
+import { useDarkThemeHook } from '@/hooks'
+
+const DESIGN_THEME = "DESIGN_THEME" 
+const storageDesign = getLocalStorage(DESIGN_THEME)
+
+export const useDesignStore = defineStore({
+  id: 'useDesignStore',
+  state: () =>
+    storageDesign || {
+      // 是否暗黑
+      ...theme,
+    },
+  getters: {
+    getDarkTheme() {
+      return this.darkTheme
+    }
+  },
+  actions: {
+    // 切换主题
+    changeTheme(e) {
+      this.darkTheme = e
+      setLocalStorage(DESIGN_THEME, this.$state)
+      useDarkThemeHook()
+    }
+  }
+})

+ 30 - 0
src/store/modules/langStore.js

@@ -0,0 +1,30 @@
+import { defineStore } from 'pinia'
+import { lang } from '@/settings/designSetting'
+import { setLocalStorage, getLocalStorage } from '@/utils'
+
+
+
+
+const LANG_STORE =  "LANG"
+const storageLang = getLocalStorage(LANG_STORE)
+
+// 语言
+export const useLangStore = defineStore({
+  id: 'useLangStore',
+  state: ()  => (
+    storageLang || {
+      lang
+    }
+  ),
+  getters: {
+    getLang() {
+      return this.lang
+    }
+  },
+  actions: {
+    changeLang(lang) { 
+      this.lang = lang
+      setLocalStorage(LANG_STORE, this.$state)
+    }
+  }
+})

+ 132 - 0
src/store/modules/systemStore.js

@@ -0,0 +1,132 @@
+import { defineStore } from "pinia";
+import { getSTSInfo } from "@/api/path/system.api";
+import { setLocalStorage, getLocalStorage } from "@/utils";
+
+const INITIALIZE = {
+  local_loading: false,
+  layout: "blendLeft",
+  menus: "",
+  routeItem: "",
+  menuTabSate: "",
+  menuSecondLongShow: false,
+  user_login_information: "",
+  stsClientInfo: {},
+};
+try {
+  const  SYSTEM_STORE = getLocalStorage("SYSTEM_STORE")
+  for (const key in SYSTEM_STORE) {
+    const value = getLocalStorage(key)
+    if (value) INITIALIZE[key] = value
+  }
+} catch (err) {
+  window.console.log('无存储数据', err)
+}
+
+export const useSystemStore = defineStore({
+  id: "useSystemStore",
+  state: () => ({
+    ...INITIALIZE,
+  }),
+  getters: {
+    getLocalLoading(){
+      return this.local_loading
+    },
+    getToken() {
+      if (this.token) return this.token;
+      this.token = window.localStorage?.token || "";
+      return this.token;
+    },
+    getLayout() {
+      return this.layout;
+    },
+    getRouteItem() {
+      return this.routeItem || getLocalStorage("routeItem")
+    },
+    getMenus() {
+      if (this.menus) return this.menus;
+      this.menus = window.localStorage?.menus || "";
+      return this.menus;
+    },
+    getMenuTabSate() {
+      return this.menuTabSate || getLocalStorage("menuTabSate");
+    },
+    getSTSClient() {
+      const stsFn = async () => {
+
+        const windowVar = window
+        let longTime = 0
+        if (windowVar.stsOSSClient && windowVar.stsOSSClient.stsTokenFreshTime) {
+          const oldTime = windowVar.stsOSSClient.stsTokenFreshTime.getTime()
+          const newTime = new Date().getTime()
+          longTime = newTime - oldTime
+        }
+
+        // STS信息
+        if (!windowVar.stsOSSClient || longTime > 10 * 60 * 1000) {
+          const res = await getSTSInfo({})
+
+          const info = res.data
+          const systemStore = useSystemStore()
+          console.log(info)
+          const getInfoData = info => {
+            const infoData = {
+              endpoint: 'https://cardiot.oss-cn-beijing.aliyuncs.com',
+              region: 'oss-cn-beijing',
+              accessKeyId: info.accessKeyId,
+              accessKeySecret: info.accessKeySecret,
+              stsToken: info.securityToken,
+              bucket: 'cardiot',
+              cname: true
+            }
+            this.stsClientInfo = infoData
+            systemStore.setStateValue('stsClientInfo', infoData)
+            return infoData
+          }
+          getInfoData(info)
+          console.log(this.stsClientInfo)
+          const client = new windowVar.OSS({
+            ...this.stsClientInfo,
+            refreshSTSToken: async () => {
+              const res = await getSTSInfo({})
+
+              getInfoData(res.data)
+
+              return {
+                accessKeyId: info.AccessKeyId,
+                accessKeySecret: info.AccessKeySecret,
+                stsToken: info.SecurityToken
+              }
+            },
+            refreshSTSTokenInterval: 30 * 60 * 1000
+          })
+
+          client.putObject = async data => {
+            const putRes = await client.put(data.key, data.body)
+            putRes.statusCode = 200
+            return putRes
+          }
+          windowVar.stsOSSClient = client
+
+          return client
+        }
+        return windowVar.stsOSSClient
+      };
+      return stsFn
+    },
+  },
+  actions: {
+    localLoading(value = false) {
+      this.local_loading = value
+    },
+    // 储存
+    setStateValue(res) {
+      this[res.key] = res.value;
+      const value =
+        typeof res.value === "string" ? res.value : JSON.stringify(res.value);
+      if (res.localStorage) {
+        // 此方法只处理了简单数据类型 如字符串(token)、布尔之类的
+        window.localStorage.setItem(res.key, value);
+      }
+    }
+  },
+});

+ 79 - 0
src/style.css

@@ -0,0 +1,79 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 0 - 0
src/utils/components.js


+ 63 - 0
src/utils/crypto.js

@@ -0,0 +1,63 @@
+import CryptoJS from 'crypto-js'
+
+
+const KEY = 'mianbaon'
+
+/**
+ * * 加密
+ * @param data { string }
+ * @returns { string } 加密后的数据
+ */
+export const cryptoEncode = (data) => {
+  const key = CryptoJS.enc.Utf8.parse(KEY);
+  const iv = CryptoJS.lib.WordArray.random(16); // 随机生成16字节的IV
+  const encrypted = CryptoJS.AES.encrypt(data, key, {
+    mode: CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7,
+    iv: iv,
+  });
+  // 将IV与加密后的数据拼接并以特定格式返回,以便在Java中分离IV和加密后的数据
+  return iv.toString(CryptoJS.enc.Base64) + encrypted.toString();
+};
+
+export const cryptoDecode = (data) => {
+  const key = CryptoJS.enc.Utf8.parse(KEY);
+  const iv = CryptoJS.lib.WordArray.create(CryptoJS.enc.Utf8.parse(KEY).words.slice(0, 4)); // 前四个字节作为IV
+  const bytes = CryptoJS.AES.decrypt(data, key, {
+      iv: iv,
+      mode: CryptoJS.mode.ECB,
+      padding: CryptoJS.pad.Pkcs7
+  });
+  return bytes.toString(CryptoJS.enc.Utf8);
+}
+
+/**
+ * golang加密
+ * @param text  { string }
+ * @param key   { string } 只能8位
+ * @returns 
+ */
+// JS DES ECB模式 加密
+export const encryptByDES = (message) =>{
+  let keyHex = CryptoJS.enc.Utf8.parse(KEY);
+  let encrypted = CryptoJS.DES.encrypt(message, keyHex, {
+      mode: CryptoJS.mode.ECB,
+      padding: CryptoJS.pad.Pkcs7
+  });
+  return encrypted.ciphertext.toString();
+}
+
+// JS DES ECB模式 解密
+export const decryptByDES = (ciphertext) => {
+  let keyHex = CryptoJS.enc.Utf8.parse(KEY);
+  let decrypted = CryptoJS.DES.decrypt({
+      ciphertext: CryptoJS.enc.Hex.parse(ciphertext)
+  }, keyHex, {
+      mode: CryptoJS.mode.ECB,
+      padding: CryptoJS.pad.Pkcs7
+  });
+  let result_value = decrypted.toString(CryptoJS.enc.Utf8);
+  return result_value;
+}
+
+

+ 5 - 0
src/utils/index.js

@@ -0,0 +1,5 @@
+export * from '@/utils/crypto'
+export * from '@/utils/utils'
+export * from '@/utils/storage'
+export * from '@/utils/router'
+export * from '@/utils/components'

+ 18 - 0
src/utils/router.js

@@ -0,0 +1,18 @@
+import router from '@/router' 
+
+
+/**
+ * * 退出
+ */
+export const fn_logout = (router) => {
+  localStorage.removeItem("user_login_information");
+  localStorage.removeItem("routeItem");
+  localStorage.removeItem("menus");
+  localStorage.removeItem("token");
+  if(router){
+    router.push({
+      name: "login"
+    })
+  }
+}
+

+ 30 - 0
src/utils/storage.js

@@ -0,0 +1,30 @@
+
+import { JSONStringify, JSONParse } from '@/utils'
+/**
+ * * 存储本地会话数据
+ * @param k 键名
+ * @param v 键值(无需stringiiy)
+ * @returns RemovableRef
+ */
+export const setLocalStorage =  (k, v) => {
+  try {
+    window.localStorage.setItem(k, JSONStringify(v))
+  } catch (error) {
+    return false
+  }
+}
+
+
+/**
+ * * 获取本地会话数据
+ * @param k 键名
+ * @returns any
+ */
+export const getLocalStorage = (k) => {
+  const item = window.localStorage.getItem(k)
+  try {
+    return item ? JSONParse(item) : item
+  } catch (err) {
+    return item
+  }
+}

+ 61 - 0
src/utils/utils.js

@@ -0,0 +1,61 @@
+/**
+ * * 生成一个不重复的ID
+ * @param { Number } randomLength
+ */
+export const getUUID = (randomLength = 10) => {
+  return 'id_' + Number(Math.random().toString().substring(2, randomLength) + Date.now()).toString(36)
+}
+
+/**
+ * * JSON序列化,支持函数和 undefined
+ * @param data
+ */
+export const JSONStringify = (data) => {
+  return JSON.stringify(
+    data,
+    (key, val) => {
+      // 处理函数丢失问题
+      if (typeof val === 'function') {
+        return `${val}`
+      }
+      // 处理 undefined 丢失问题
+      if (typeof val === 'undefined') {
+        return null
+      }
+      return val
+    },
+    2
+  )
+}
+/**
+ * * JSON反序列化,支持函数和 undefined
+ * @param data
+ */
+
+export const JSONParse = (data) => {
+  if (data.trim() === '') return
+  return JSON.parse(data, (k, v) => {
+    if (k !== 'formatter') {
+      return v
+    }
+    // 还原函数值
+    if (typeof v === 'string' && checkJsonStringify(v)) {
+      return eval2(`(function(){return ${v}})()`)
+    } else if (typeof v === 'string' && v.indexOf && v.indexOf('return ') > -1) {
+      const baseLeftIndex = v.indexOf('(')
+      if (baseLeftIndex > -1) {
+        const newFn = `function ${v.substring(baseLeftIndex)}`
+        return eval2(`(function(){return ${newFn}})()`)
+      }
+    }
+    return v
+  })
+}
+
+export const getParseLang = (data, lang) => {
+  try {
+    return JSON.parse(data)[lang];
+  } catch (error) {
+    return data;
+  }
+};

+ 53 - 0
src/views/exception/errors.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="container">
+    <img class="error-image" src="@/assets/images/error-image.png" />
+    <div class="container-right">
+      <div class="error-mainInfoTwo">{{ defaultObj.error_two }}</div>
+      <a-button style="margin-top: 30px" type="primary" @click="evBack">{{ defaultObj.button_text }}</a-button>
+    </div>
+  </div>
+</template>
+
+<script  setup>
+import { useRoute, useRouter } from 'vue-router'
+
+const router = useRouter()
+const route = useRoute()
+
+const defaultObj = {
+  button_text: '回到上一页',
+  error_code: 404,
+  error_two: '页面不存在',
+  hasShowButton: true,
+}
+
+const evBack = () => {
+  //
+  router.go(-1)
+  // window.console.log('跳转')
+}
+</script>
+
+<style lang="less" scoped>
+.container {
+  display: flex !important;
+  flex-direction: row !important;
+  min-height: 100vh;
+  flex: auto;
+  background-color: @bg_color_4;
+  align-items: center !important;
+  justify-content: center !important;
+}
+.container-right {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  justify-content: space-around;
+  margin-left: 90px;
+}
+.error-mainInfoTwo {
+  font-size: 36px;
+  font-weight: bold;
+  color: @text_color_1;
+}
+</style>

+ 118 - 0
src/views/login/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="container">
+    <div class="logo">
+      <!-- <img alt="logo" :src="carouselItem.image" style="height: 50px; border-radius: 50%" /> -->
+      <div class="logo-text">Management Platform</div>
+    </div>
+
+
+    <div class="banner">
+      <div  class="carousel-item">
+        <div class="carousel-title">{{ carouselItem.slogan }}</div>
+        <div class="carousel-sub-title">{{ carouselItem.subSlogan }}</div>
+        <img class="carousel-image" :src="carouselItem.image" />
+      </div>
+    </div>
+
+    <div class="content">
+      <div class="content-inner">
+        <LoginForm />
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<script setup>
+import { onMounted } from "vue"
+import LoginForm from './login-form.vue'
+
+import bannerImage from '@/assets/images/login-banner.png'
+const  carouselItem = {
+  slogan: 'Admin 管理平台',
+  subSlogan: 'Management Platform',
+  image: bannerImage,
+}
+</script>
+
+<style scoped lang="less">
+.container {
+  display: flex;
+  height: 100vh;
+  min-width: 1000px;
+  flex-direction: row;
+  .banner {
+    width: 550px;
+    background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
+  }
+
+  .content {
+    position: relative;
+    display: flex;
+    flex: 1;
+    align-items: center;
+    justify-content: center;
+    padding-bottom: 40px;
+    background: @bg_color_2;
+  }
+
+  .footer {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 100%;
+  }
+}
+.logo {
+  position: fixed;
+  top: 24px;
+  left: 22px;
+  z-index: 1;
+  display: inline-flex;
+  align-items: center;
+
+  &-text {
+    margin-right: 4px;
+    margin-left: 4px;
+    color: @bg_color_6;
+    font-size: 20px;
+  }
+}
+
+.banner {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.carousel {
+  height: 100%;
+
+  &-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+  }
+
+  &-title {
+    color: @bg_color_6;
+    font-weight: 500;
+    font-size: 20px;
+    line-height: 28px;
+  }
+
+  &-sub-title {
+    margin-top: 8px;
+    color: @text_color_2;
+    font-size: 14px;
+    line-height: 22px;
+  }
+
+  &-image {
+    width: 320px;
+    margin-top: 30px;
+  }
+}
+</style>
+

+ 129 - 0
src/views/login/login-form.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="login-form-wrapper">
+    <div class="login-form-title">登陆 Admin</div>
+    <div class="login-form-sub-title">Login to ADMIN</div>
+    
+    <a-form ref="loginForm" :model="formData" class="login-form"  @submit-success="handleSubmit">
+      <a-form-item
+        field="username"
+        :rules="[{ required: true, message: '用户名不能为空' }]"
+        :validate-trigger="['change', 'blur']"
+        hide-label
+      >
+        <a-input v-model="formData.username" placeholder="用户名:">
+          <template #prefix>
+            <icon-user />
+          </template>
+        </a-input>
+      </a-form-item>
+      <a-form-item
+        field="password"
+        :rules="[{ required: true, message: '密码不能为空' }]"
+        :validate-trigger="['change', 'blur']"
+        hide-label
+        
+      >
+        <a-input-password v-model="formData.password" placeholder="密码:" allow-clear>
+          <template #prefix>
+            <icon-lock />
+          </template>
+        </a-input-password>
+      </a-form-item>
+    
+
+      <a-space :size="16" direction="vertical">
+        <div class="login-form-password-actions">
+          <a-checkbox checked="rememberPassword" :default-checked="true" @change="setRememberPassword">
+            记住账号
+          </a-checkbox>
+          <!-- <a-link>忘记密码</a-link> -->
+        </div>
+        <a-button type="primary" html-type="submit" long :loading="formLoading"> 登陆 </a-button>
+      </a-space>
+    </a-form>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { encryptByDES } from "@/utils"
+import { loginApi } from "@/api/path/login.api"
+import { useSystemStore  } from "@/store/modules/systemStore"
+import { useRouter } from 'vue-router'
+import { updateRouteByMenu } from '@/router/router.update'
+
+
+const router = useRouter()
+const formData = ref({
+  username:localStorage.remember_user_name
+})
+const formLoading = ref(false)
+const rememberPassword = ref(true)
+
+const systemStore = useSystemStore()
+
+const handleSubmit = async () => {
+  const { data } = await loginApi({
+    username: formData.value.username,
+    password: encryptByDES(formData.value.password)
+  })
+  window.localStorage.setItem('remember_user_name', formData.value.username)
+ 
+  systemStore.setStateValue({
+    key: 'token',
+    value: data.token,
+    localStorage: true,
+  })
+  systemStore.setStateValue({
+    key: 'user_login_information',
+    value: JSON.stringify(data.userInfo),
+    localStorage: true,
+  })
+  await updateRouteByMenu(router, systemStore)
+  router.push({
+    path: "/",
+  })
+}
+
+const setRememberPassword = (e) => {
+  if (!e) {
+    window.localStorage.removeItem('remember_user_name')
+  }
+}
+</script>
+
+<style lang="less" scoped>
+
+.login-form-wrapper{
+  margin: 0 1rem;
+}
+.login-form {
+  margin-top: 32px;
+  &-wrapper {
+    width: 320px;
+  }
+
+  &-title {
+    color:  @text_color_1;
+    font-weight: 500;
+    font-size: 24px;
+    line-height: 32px;
+  }
+
+  &-sub-title {
+    color: @text_color_2;
+    font-size: 16px;
+    line-height: 24px;
+  }
+
+  &-password-actions {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 15px;
+  }
+
+  &-register-btn {
+    color: @text_color_3 !important;
+  }
+}
+</style>

+ 155 - 0
src/views/overflowalarm/index.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="overflow-alarm">
+    <!-- 搜索条件区 -->
+    <div class="search-section">
+      <a-form :model="searchForm" layout="inline">
+        <a-form-item field="iccid" label="ICCID">
+          <a-input v-model="searchForm.iccid" placeholder="请输入ICCID" allow-clear />
+        </a-form-item>
+        <a-form-item field="cardNumber" label="卡号">
+          <a-input v-model="searchForm.cardNumber" placeholder="请输入卡号" allow-clear />
+        </a-form-item>
+        <a-form-item field="usageStatus" label="使用状态">
+          <a-select
+            v-model="searchForm.usageStatus"
+            placeholder="请选择使用状态"
+            allow-clear
+            style="width: 160px"
+          >
+            <a-option v-for="status in usageStatusOptions" :key="status.value" :value="status.value">
+              {{ status.label }}
+            </a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item field="channel" label="通道">
+          <a-select
+            v-model="searchForm.channel"
+            placeholder="请选择通道"
+            allow-clear
+            style="width: 160px"
+          >
+            <a-option v-for="channel in channelOptions" :key="channel.value" :value="channel.value">
+              {{ channel.label }}
+            </a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <a-space>
+            <a-button type="primary" @click="handleSearch">搜索</a-button>
+            <a-button @click="resetSearch">重置</a-button>
+          </a-space>
+        </a-form-item>
+      </a-form>
+    </div>
+  
+    <!-- 数据表格 -->
+    <a-table :columns="columns" :data="tableData" :pagination="pagination" :scroll="{ x: '100%', y: '400px' }">
+      <template #usageStatus="{ record }">
+        <a-tag :color="getStatusColor(record.usageStatus)">{{ record.usageStatus }}</a-tag>
+      </template>
+    </a-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { Message } from '@arco-design/web-vue';
+
+const searchForm = reactive({
+  iccid: '',
+  cardNumber: '',
+  usageStatus: '',
+  channel: '',
+});
+
+const usageStatusOptions = [
+  { label: '正常', value: 'normal' },
+  { label: '停机', value: 'suspended' },
+];
+
+const channelOptions = [
+  { label: '通道1', value: 'channel1' },
+  { label: '通道2', value: 'channel2' },
+];
+
+const columns = [
+  { title: 'ICCID', dataIndex: 'iccid' },
+  { title: '卡号', dataIndex: 'cardNumber' },
+  { title: '使用状态', slotName: 'usageStatus' },
+  { title: '通道', dataIndex: 'channel' },
+  { title: '到期日期', dataIndex: 'expirationDate' },
+  { title: '客户名称', dataIndex: 'customerName' },
+  { title: '套餐已用量', dataIndex: 'usedData' },
+  { title: '本月月量', dataIndex: 'monthlyData' },
+  { title: '超套用量', dataIndex: 'overUsage' },
+  { title: '资费合计', dataIndex: 'totalCost' },
+];
+
+const tableData = ref([
+  {
+    iccid: '898600000000001',
+    cardNumber: '13800138000',
+    usageStatus: '正常',
+    channel: '通道1',
+    expirationDate: '2023-12-31',
+    customerName: '张三',
+    usedData: '5GB',
+    monthlyData: '10GB',
+    overUsage: '0GB',
+    totalCost: '50元',
+  },
+  {
+    iccid: '898600000000002',
+    cardNumber: '13900139000',
+    usageStatus: '停机',
+    channel: '通道2',
+    expirationDate: '2024-06-30',
+    customerName: '李四',
+    usedData: '8GB',
+    monthlyData: '5GB',
+    overUsage: '3GB',
+    totalCost: '80元',
+  },
+]);
+
+const pagination = reactive({
+  total: 100,
+  current: 1,
+  pageSize: 10,
+});
+
+const getStatusColor = (status) => {
+  const colorMap = {
+    '正常': 'green',
+    '停机': 'red',
+  };
+  return colorMap[status] || 'blue';
+};
+
+const handleSearch = () => {
+  console.log('Search form data:', searchForm);
+  Message.success('执行搜索操作');
+};
+
+const resetSearch = () => {
+  Object.keys(searchForm).forEach(key => {
+    searchForm[key] = '';
+  });
+  Message.success('搜索条件已重置');
+};
+</script>
+
+<style scoped>
+.overflow-alarm {
+  padding: 20px;
+}
+
+.search-section {
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+.arco-table-th {
+  white-space: nowrap;
+}
+</style>

+ 171 - 0
src/views/plan-management/NewFeeForm.vue

@@ -0,0 +1,171 @@
+<template>
+  <a-modal
+    :visible="visible"
+    :title="editMode ? $t('plan.editFee') : $t('plan.addFee')"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+    :width="800"
+  >
+    <a-form :model="formData" :rules="rules" ref="formRef" >
+      <a-divider>{{ $t('plan.basicInfo') }}</a-divider>
+      <a-form-item field="feeCode" :label="$t('plan.feeCode')" required>
+        <a-input v-model="formData.feeCode" :placeholder="$t('plan.enterFeeCode')" :maxLength="60" show-word-limit />
+      </a-form-item>
+      <a-form-item field="feeName" :label="$t('plan.feeName')" required>
+        <a-input v-model="formData.feeName" :placeholder="$t('plan.enterFeeName')" :maxLength="60" show-word-limit />
+      </a-form-item>
+      <a-form-item field="supplier" :label="$t('plan.supplier')" required>
+        <a-select v-model="formData.supplier" :placeholder="$t('plan.selectSupplier')">
+          <a-option value="supplier1">{{ $t('plan.supplier1') }}</a-option>
+          <a-option value="supplier2">{{ $t('plan.supplier2') }}</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="dataType" :label="$t('plan.dataType')" required>
+        <a-radio-group v-model="formData.dataType">
+          <a-radio value="general">{{ $t('plan.general') }}</a-radio>
+          <a-radio value="specific">{{ $t('plan.specific') }}</a-radio>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item field="billingCategory" :label="$t('plan.billingCategory')" required>
+        <a-radio-group v-model="formData.billingCategory">
+          <a-radio value="data">{{ $t('plan.data') }}</a-radio>
+          <a-radio value="voice">{{ $t('plan.voice') }}</a-radio>
+          <a-radio value="nb">{{ $t('plan.nb') }}</a-radio>
+          <a-radio value="sms">{{ $t('plan.sms') }}</a-radio>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item field="status" :label="$t('plan.status')" required>
+        <a-radio-group v-model="formData.status">
+          <a-radio value="normal">{{ $t('plan.status.normal') }}</a-radio>
+          <a-radio value="disabled">{{ $t('plan.status.disabled') }}</a-radio>
+          <a-radio value="offline">{{ $t('plan.status.offline') }}</a-radio>
+        </a-radio-group>
+      </a-form-item>
+
+      <a-divider>{{ $t('plan.billingInfo') }}</a-divider>
+      <a-form-item field="billingCycle" :label="$t('plan.billingCycle')" required>
+        <a-radio-group v-model="formData.billingCycle">
+          <a-radio value="daily">{{ $t('plan.daily') }}</a-radio>
+          <a-radio value="monthly">{{ $t('plan.monthly') }}</a-radio>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item field="packageSize" :label="$t('plan.packageSize')" required>
+        <a-input-number v-model="formData.packageSize" :placeholder="$t('plan.enterPackageSize')" style="width: 200px" />
+        <a-select v-model="formData.packageUnit" style="width: 80px; margin-left: 10px;">
+          <a-option value="KB">KB</a-option>
+          <a-option value="MB">MB</a-option>
+          <a-option value="GB">GB</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="standardPrice" :label="$t('plan.standardPrice')" required>
+        <a-input-number v-model="formData.standardPrice" :placeholder="$t('plan.enterPrice')" style="width: 200px" />
+        <span style="margin: 0 10px;">{{ $t('plan.currency') }}/</span>
+        <a-select v-model="formData.priceUnit" style="width: 120px;">
+          <a-option value="day">{{ $t('plan.billingCycleType') }}</a-option>
+          <a-option value="month">{{ $t('plan.month') }}</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="subscriptionPeriod" :label="$t('plan.subscriptionPeriod')" required>
+        <a-input-number v-model="formData.minPeriod" :placeholder="$t('plan.minimum')" style="width: 100px" />
+        <span style="margin: 0 10px;">{{ $t('plan.monthsMin') }}</span>
+        <a-input-number v-model="formData.maxPeriod" :placeholder="$t('plan.maximum')" style="width: 100px" />
+        <span style="margin-left: 10px;">{{ $t('plan.monthsMax') }}</span>
+      </a-form-item>
+      <a-form-item field="overagePrice" :label="$t('plan.overagePrice')">
+        <a-input-number v-model="formData.overagePrice" :placeholder="$t('plan.enterOveragePrice')" style="width: 200px" />
+        <span style="margin: 0 10px;">{{ $t('plan.currency') }}/</span>
+        <a-select v-model="formData.overageUnit" style="width: 80px;">
+          <a-option value="KB">KB</a-option>
+          <a-option value="MB">MB</a-option>
+          <a-option value="GB">GB</a-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item field="connectionTimes" :label="$t('plan.connectionTimes')">
+        <a-input-number v-model="formData.connectionTimes" placeholder="0" />
+        <span style="margin-left: 10px;">{{ $t('plan.times') }}</span>
+      </a-form-item>
+      <a-form-item field="voiceRate" :label="$t('plan.voiceRate')">
+        <a-input-number v-model="formData.voiceRate" placeholder="0" />
+        <span style="margin-left: 10px;">{{ $t('plan.currency') }}/{{ $t('plan.minute') }}</span>
+      </a-form-item>
+      <a-form-item field="billingCode" :label="$t('plan.billingCode')">
+        <a-input v-model="formData.billingCode" :placeholder="$t('plan.autoGenerated')" disabled />
+      </a-form-item>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+const props = defineProps({
+  visible: Boolean,
+  editMode: Boolean,
+  editData: Object,
+});
+
+const emit = defineEmits(['update:visible', 'submit']);
+
+const formRef = ref(null);
+
+const formData = reactive({
+  feeCode: '',
+  feeName: '',
+  supplier: '',
+  dataType: 'general',
+  billingCategory: 'data',
+  status: 'normal',
+  billingCycle: 'daily',
+  packageSize: null,
+  packageUnit: 'MB',
+  standardPrice: null,
+  priceUnit: 'day',
+  minPeriod: 1,
+  maxPeriod: 60,
+  overagePrice: null,
+  overageUnit: 'MB',
+  connectionTimes: 0,
+  voiceRate: 0,
+  billingCode: t('plan.autoGenerated'),
+});
+
+const rules = {
+  feeCode: [{ required: true, message: t('plan.feeCodeRequired') }],
+  feeName: [{ required: true, message: t('plan.feeNameRequired') }],
+  supplier: [{ required: true, message: t('plan.supplierRequired') }],
+  dataType: [{ required: true, message: t('plan.dataTypeRequired') }],
+  billingCategory: [{ required: true, message: t('plan.billingCategoryRequired') }],
+  status: [{ required: true, message: t('plan.statusRequired') }],
+  billingCycle: [{ required: true, message: t('plan.billingCycleRequired') }],
+  packageSize: [{ required: true, type: 'number', message: t('plan.packageSizeRequired') }],
+  standardPrice: [{ required: true, type: 'number', message: t('plan.standardPriceRequired') }],
+  minPeriod: [{ required: true, type: 'number', message: t('plan.minPeriodRequired') }],
+  maxPeriod: [{ required: true, type: 'number', message: t('plan.maxPeriodRequired') }],
+};
+
+const handleSubmit = () => {
+  formRef.value.validate((errors) => {
+    if (!errors) {
+      emit('submit', { ...formData });
+      emit('update:visible', false);
+    } else {
+      console.error('Validation failed', errors);
+      Message.error(t('plan.fillRequiredFields'));
+    }
+  });
+};
+
+const handleCancel = () => {
+  emit('update:visible', false);
+};
+</script>
+
+<style scoped>
+.arco-form-item {
+  margin-bottom: 18px;
+}
+</style>

+ 176 - 0
src/views/plan-management/index.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="fee-management">
+    <!-- Search section -->
+    <div class="search-section">
+      <a-form :model="searchForm" layout="inline">
+        <a-form-item field="feeName" :label="$t('plan.feeName')">
+          <a-input v-model="searchForm.feeName" :placeholder="$t('plan.enterFeeName')" allow-clear />
+        </a-form-item>
+        <a-form-item field="operatorType" :label="$t('plan.operatorType')">
+          <a-select
+            v-model="searchForm.operatorType"
+            :placeholder="$t('plan.selectOperatorType')"
+            allow-clear
+            style="width: 200px"
+          >
+            <a-option v-for="type in operatorTypes" :key="type.value" :value="type.value">
+              {{ $t(`plan.operatorTypes.${type.value}`) }}
+            </a-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item>
+          <a-button type="primary" @click="handleSearch">{{ $t('global.common.search') }}</a-button>
+        </a-form-item>
+      </a-form>
+    </div>
+
+    <!-- Add button -->
+    <div class="operation-section">
+      <a-button type="primary" @click="showNewFeeForm">{{ $t('plan.addFee') }}</a-button>
+    </div>
+
+    <!-- Data table -->
+    <a-table :columns="columns" :data="tableData" :pagination="pagination" @page-change="onPageChange">
+      <template #status="{ record }">
+        <a-tag :color="getStatusColor(record.status)">{{ $t(`plan.status.${record.status}`) }}</a-tag>
+      </template>
+      <template #operation="{ record }">
+        <a-space>
+          <a-button type="text" size="small" @click="handleEdit(record)">{{ $t('global.common.edit') }}</a-button>
+          <a-button type="text" size="small" @click="handleDelete(record)">{{ $t('global.common.delete') }}</a-button>
+        </a-space>
+      </template>
+    </a-table>
+
+    <!-- New/Edit fee form dialog -->
+    <new-fee-form 
+      v-model:visible="newFeeFormVisible"
+      :editMode="editMode"
+      :editData="editData"
+      @submit="handleFeeSubmit"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { Message } from '@arco-design/web-vue';
+import { useI18n } from 'vue-i18n';
+import NewFeeForm from './NewFeeForm.vue';
+
+const { t } = useI18n();
+
+const searchForm = reactive({
+  feeName: '',
+  operatorType: '',
+});
+
+const operatorTypes = [
+  { value: 'general' },
+  { value: 'specific' },
+];
+
+const columns = computed(() => [
+  { title: t('plan.id'), dataIndex: 'id' },
+  { title: t('plan.feeCode'), dataIndex: 'feeCode' },
+  { title: t('plan.feeName'), dataIndex: 'feeName' },
+  { title: t('plan.supplierName'), dataIndex: 'supplierName' },
+  { title: t('plan.dataType'), dataIndex: 'dataType' },
+  { title: t('plan.billingCategory'), dataIndex: 'billingCategory' },
+  { title: t('plan.billingCycle'), dataIndex: 'billingCycle' },
+  { title: t('plan.packageSize'), dataIndex: 'packageSize' },
+  { title: t('plan.standardPrice'), dataIndex: 'standardPrice' },
+  { title: t('plan.cycleLimitation'), dataIndex: 'cycleLimitation' },
+  { title: t('plan.status'), slotName: 'status' },
+  { title: t('global.common.operations'), slotName: 'operation', width: 150 },
+]);
+
+const tableData = ref([
+  {
+    id: 8,
+    feeCode: 'MR026',
+    feeName: '移动200G',
+    supplierName: '泰国True',
+    dataType: '通用',
+    billingCategory: '流量',
+    billingCycle: '按天',
+    packageSize: '2.00G',
+    standardPrice: '150.00元/月',
+    cycleLimitation: '1-12月',
+    status: '正常',
+  },
+  // 添加更多模拟数据...
+]);
+
+const pagination = reactive({
+  total: 100,
+  current: 1,
+  pageSize: 10,
+});
+
+const newFeeFormVisible = ref(false);
+const editMode = ref(false);
+const editData = ref(null);
+
+const handleSearch = () => {
+  console.log('Search form data:', searchForm);
+  Message.success(t('plan.searchExecuted'));
+};
+
+const showNewFeeForm = () => {
+  editMode.value = false;
+  editData.value = null;
+  newFeeFormVisible.value = true;
+};
+
+const handleEdit = (record) => {
+  editMode.value = true;
+  editData.value = { ...record };
+  newFeeFormVisible.value = true;
+};
+
+const handleDelete = (record) => {
+  Message.success(t('plan.feeDeleted', { name: record.feeName }));
+};
+
+const handleFeeSubmit = (formData) => {
+  if (editMode.value) {
+    console.log('Edited fee submitted:', formData);
+    Message.success(t('plan.feeUpdated', { name: formData.feeName }));
+  } else {
+    console.log('New fee submitted:', formData);
+    Message.success(t('plan.feeAdded', { name: formData.feeName }));
+  }
+};
+
+const onPageChange = (page) => {
+  pagination.current = page;
+};
+
+const getStatusColor = (status) => {
+  const colorMap = {
+    '正常': 'green',
+    '禁用': 'red',
+    '下架': 'gray',
+  };
+  return colorMap[status] || 'blue';
+};
+</script>
+
+<style scoped>
+.fee-management {
+  padding: 20px;
+}
+
+.search-section {
+  margin-bottom: 20px;
+}
+
+.operation-section {
+  margin-bottom: 20px;
+}
+
+.fee-management .arco-table-th {
+  white-space: nowrap;
+}
+</style>

+ 114 - 0
src/views/staging/index.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="container">
+    <div class="head-title">
+      <span> {{ route.meta.title }} </span>
+    
+    </div>
+    <div>
+      <a-card
+        v-for="item in componentData"
+        :key="item.id"
+        :style="{ width: '360px' }"
+      >
+        <template #actions>
+          <span class="icon-hover"> <IconThumbUp /> </span>
+          <span class="icon-hover"> <icon-delete /> </span>
+          <span class="icon-hover" @click="openCode(item)">  <icon-code /> </span>
+        </template>
+        <template #cover>
+          <div
+            :style="{
+              height: '204px',
+              overflow: 'hidden',
+            }"
+          >
+            <img
+              :style="{ width: '100%', transform: 'translateY(-20px)' }"
+              alt="dessert"
+              :src="item.thumbnail"
+            />
+          </div>
+        </template>
+
+        <a-card-meta :title="item.title" :description="item.introduce">
+          <template #avatar>
+            <div
+              :style="{
+                display: 'flex',
+                alignItems: 'center',
+                color: '#1D2129',
+              }"
+            >
+              <a-avatar :size="30" :style="{ marginRight: '8px' }">
+                <img
+                  v-if="item.avatar"
+                  class="avatar-image"
+                  alt="avatar"
+                  :src="item.avatar"
+                />
+                <span v-else>{{ item.name.charAt(0).toUpperCase() }}</span>
+              </a-avatar>
+              <a-typography-text>{{ item.name }}</a-typography-text>
+            </div>
+          </template>
+        </a-card-meta>
+      </a-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { componentList } from "@/api/path/component.api";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+
+const componentData = ref([]);
+
+const initData = async () => {
+  const { data } = await componentList({});
+  componentData.value = data;
+};
+
+const openCode = (codeData) => {
+  console.log(codeData)
+}
+
+onMounted(() => {
+  initData();
+});
+</script>
+
+<style scoped lang="less">
+.head-title {
+  display: flex;
+  justify-content: space-between;
+  .fn-headTitleDiv();
+  padding: 0.5rem 0;
+  margin-bottom: 1rem;
+}
+.icon-hover {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  transition: all 0.1s;
+}
+.icon-hover:hover {
+  background-color: rgb(var(--gray-2));
+}
+:deep(.arco-avatar-image){
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.avatar-image{
+  background-color: #000 !important;
+  border-radius: 50%;
+  width: 28px;
+  height: 28px;
+}
+</style>

+ 128 - 0
src/views/system/dictionary/DictionaryForm.vue

@@ -0,0 +1,128 @@
+<template>
+    <a-modal
+      :visible="visible"
+      :title="isEdit ? $t('form.Edit') : $t('form.Add')"
+      @cancel="handleCancel"
+      :ok-button-props="{ onClick: handleSubmit }"
+      :cancel-button-props="{ onClick: handleCancel }"
+    >
+      <a-form ref="formRef" :model="form" :rules="rules">
+        <a-form-item
+          field="label"
+          :label="$t('form.dictionaryName')"
+          label-col-flex="100px"
+        >
+          <a-input v-model="form.label" />
+        </a-form-item>
+        <a-form-item
+          field="value"
+          :label="$t('form.dictionaryValue')"
+          label-col-flex="100px"
+        >
+          <a-input v-model="form.value" />
+        </a-form-item>
+        <a-form-item
+          field="typeLabel"
+          :label="$t('form.dictionaryType')"
+          label-col-flex="100px"
+        >
+          <a-input v-model="form.typeLabel" />
+        </a-form-item>
+        <a-form-item
+          field="typeId"
+          :label="$t('form.dictionaryType') + ' ID'"
+          label-col-flex="100px"
+        >
+          <a-input v-model="form.typeId" />
+        </a-form-item>
+        <a-form-item
+          field="description"
+          :label="$t('form.dictionaryDescription')"
+          label-col-flex="100px"
+        >
+          <a-textarea v-model="form.description" />
+        </a-form-item>
+        <a-form-item
+          field="sortNo"
+          :label="$t('form.dictionarySortNumber')"
+          label-col-flex="100px"
+        >
+          <a-input-number v-model="form.sortNo" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </template>
+  
+  <script setup>
+  import { ref, reactive, watch } from 'vue';
+
+  const props = defineProps({
+    visible: Boolean,
+    isEdit: Boolean,
+    editData: Object,
+  });
+
+  const emit = defineEmits(['update:visible', 'submit']);
+
+  const formRef = ref(null);
+
+  const initialFormState = {
+    id: null,
+    label: '',
+    value: '',
+    typeLabel: '',
+    typeId: '',
+    description: '',
+    sortNo: 0,
+  };
+
+  const form = reactive({...initialFormState});
+
+  // 监听 visible 和 editData 变化
+  watch([() => props.visible, () => props.editData], ([newVisible, newEditData]) => {
+    if (newVisible) {
+      if (props.isEdit && newEditData) {
+        Object.assign(form, newEditData);
+      } else {
+        Object.assign(form, initialFormState);
+      }
+    }
+  }, { immediate: true });
+
+  const rules = {
+    label: [
+      { required: true, message: $t('form.PleaseEnterThe') + $t('form.dictionaryName') }
+    ],
+    value: [
+      { required: true, message: $t('form.PleaseEnterThe') + $t('form.dictionaryValue') }
+    ],
+    typeLabel: [
+      { required: true, message: $t('form.PleaseEnterThe') + $t('form.dictionaryType') }
+    ],
+    typeId: [
+      { required: true, message: $t('form.PleaseEnterThe') + $t('form.dictionaryType') + ' ID' }
+    ],
+    sortNo: [
+      { required: true, message: $t('form.PleaseEnterThe') + $t('form.dictionarySortNumber') }
+    ]
+  };
+
+  const handleCancel = () => {
+    emit('update:visible', false);
+  };
+
+  const handleSubmit = () => {
+    formRef.value.validate((errors) => {
+      if (!errors) {
+        console.log(props.isEdit ? 'Editing dictionary item:' : 'Adding new dictionary item:', form);
+        emit('submit', { ...form });
+        emit('update:visible', false);
+        // 提交后清空表单
+        Object.assign(form, initialFormState);
+      } else {
+        console.error('Validation failed', errors);
+      }
+    });
+  };
+  </script>
+

+ 95 - 0
src/views/system/dictionary/config.js

@@ -0,0 +1,95 @@
+
+export const columns =  [
+  {
+    title:  window.$t('form.dictionarySortNumber'),
+    align: "center",
+    width: 80,
+    dataIndex: "sortNo",
+    slotName: 'sortNo',
+  },
+  {
+    title:  window.$t('form.dictionaryName'),
+    dataIndex: "label",
+    width: 100,
+    slotName: "label"
+  },
+  {
+    title:  window.$t('form.dictionaryValue'),
+    dataIndex: "typeId",
+    width: 100,
+    align: "center",
+  },
+  {
+    title:  window.$t('form.dictionaryType'),
+    dataIndex: "typeLabel",
+    width: 100,
+    align: "center",
+  },
+  {
+    title:   window.$t('form.dictionaryDescription'),
+    dataIndex: "description",
+    width: 200,
+  },
+  {
+    title:  window.$t('form.updateUser'),
+    width: 260,
+    dataIndex: "updateUserId",
+  },
+  {
+    title:  window.$t('form.createUser'),
+    width: 260,
+    dataIndex: "createUserId",
+  },
+  {
+    title:  window.$t('form.createTime'),
+    width: 260,
+    dataIndex: "createdAt",
+  },
+  {
+    title:  window.$t('form.updateTime'),
+    width: 260,
+    dataIndex: "updatedAt",
+  },
+  {
+    title:  window.$t('form.Id'),
+    dataIndex: "id",
+    align: "center",
+    width: 180,
+    fixed: "right",
+    slotName: 'id',
+  },
+];
+
+
+export const findConfig = [
+  {
+    label:   window.$t('form.dictionaryName'),
+    key: "name",
+    type: 'input',
+    placeholder: '请输入名称'
+  },
+  {
+    label:  window.$t('form.dictionaryType'),
+    key: "type",
+    type: 'select',
+    keys: {
+      label: 'label',
+      key: 'value'
+    },
+    placeholder: '请选择类型',
+    options: [
+      {
+        label: '类型1',
+        value: '1'
+      },
+      {
+        label: '类型2',
+        value: '2'
+      },
+      {
+        label: '类型3',
+        value: '3'
+      }
+    ]
+  },
+]

+ 118 - 0
src/views/system/dictionary/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="container">
+    <div class="head-title">
+      <span> {{ route.meta.title }} </span>
+      <a-button type="primary" size="mini" @click="showAddForm">{{
+        $t("form.Add")
+      }}</a-button>
+    </div>
+
+    <find-head  :list="findConfig" @onSearch="evSearch" />
+    <a-table :scroll="{ x: '120%' }" :columns="columns" :data="tableData" >
+
+      <template #id="{ record }">
+          <a
+            href="javascript:;"
+            class="a-link"
+            style="margin-right: 1rem"
+            @click="evEdit(record)"
+            >{{$t('form.Edit')}}</a
+          >
+          <a-popconfirm
+            :content="$t('form.DeleteConfirm')"
+            :ok-text="$t('form.Confirm')"
+            :cancel-text="$t('form.Cancel')"
+            @ok="evDelete(record.id)"
+          >
+            <a href="javascript:;" class="a-link">{{$t('form.Delete')}}</a>
+        </a-popconfirm>
+        </template>
+    </a-table>
+
+    <DictionaryForm
+      v-model:visible="formVisible"
+      :is-edit="isEdit"
+      :edit-data="editData"
+      @submit="handleSubmitDictionary"
+    />
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, computed, ref, watch } from "vue";
+import { useRoute } from "vue-router";
+import FindHead from "@/components/FindHead/index.vue"
+import { columns, findConfig } from "./config";
+import { getDictionaryList, getDictionaryById, addDictionary, updateDictionary } from "@/api/path/dictionary";
+import { getInitPagination } from "@/settings/pagination";
+import DictionaryForm from "./DictionaryForm.vue";
+
+const route = useRoute();
+
+const tableData = ref([]);
+const findData = ref({})
+const pagination = ref(getInitPagination)
+const formVisible = ref(false);
+const isEdit = ref(false);
+const editData = ref(null);
+
+const showAddForm = () => {
+  isEdit.value = false;
+  editData.value = null;
+  formVisible.value = true;
+};
+
+const evSearch = (data) => {
+  initTableData()
+  console.log("evSearch", data);
+}
+
+const initTableData = async () => {
+  const {data, total} = await getDictionaryList({
+    size: pagination.value.pageSize,
+    current: pagination.value.current,
+    ...findData.value,
+  }) 
+  tableData.value = data.records
+  findConfig[1].options = data.records.map(item => ({
+    label: item.typeLabel,
+    value: item.typeId,
+  }))
+  pagination.value.total = data.total
+}
+
+const evEdit = async (record) => {
+  isEdit.value = true;
+  // 模拟获取详细数据的 API 调用
+  const detailData = await getDictionaryById(record.id);
+  editData.value = detailData;
+  formVisible.value = true;
+};
+
+const handleSubmitDictionary = async (formData) => {
+  if (isEdit.value) {
+    // 模拟更新 API 调用
+    await updateDictionary(formData);
+    console.log('Dictionary item updated:', formData);
+  } else {
+    // 模拟添加 API 调用
+    await addDictionary(formData);
+    console.log('New dictionary item added:', formData);
+  }
+  // 重新加载表格数据
+  await initTableData();
+};
+
+onMounted(() => {
+  initTableData()
+});
+</script>
+
+<style lang="less" scoped>
+.head-title {
+  display: flex;
+  justify-content: space-between;
+  .fn-headTitleDiv();
+  padding: 0.5rem 0;
+}
+</style>

+ 50 - 0
src/views/system/menu/config.js

@@ -0,0 +1,50 @@
+export const columns  =  [
+  {
+    title:  window.$t('form.Name'),
+    dataIndex: "name",
+    slotName: "name",
+    width: 200, 
+    fixed:"left"
+  },
+  {
+    title:  window.$t('form.Icon'),
+    align: "center",
+    width: 100,
+    dataIndex: "icon",
+    slotName: 'icon',
+ 
+  },
+  {
+    title:  window.$t('form.SortNumber'),
+    dataIndex: "sortNumber",
+    width: 100,
+    align: "center",
+  },
+
+  {
+    title:  window.$t('form.Refresh'),
+    dataIndex: "refresh",
+    width: 100,
+    slotName: 'refresh',
+    align: "center"
+  },
+  {
+    title:   window.$t('form.Path'),
+    dataIndex: "path",
+    width: 200,
+  },
+  {
+    title:  window.$t('form.Url'),
+    width: 260,
+    dataIndex: "url",
+  },
+ 
+  {
+    title:  window.$t('form.Id'),
+    dataIndex: "id",
+    align: "center",
+    width: 180,
+    fixed: "right",
+    slotName: 'id',
+  },
+];

+ 550 - 0
src/views/system/menu/index.vue

@@ -0,0 +1,550 @@
+<template>
+  <div class="container">
+    <div class="head-title">
+      <span> {{ route.meta.title }} </span>
+      <a-button type="primary" @click="evAddMenu">{{ $t('global.newMenu') }}</a-button>
+    </div>
+
+     <a-space direction="vertical" :style="{ width: '100%' }">
+      <a-upload  :show-file-list="false"  :custom-request="customRequest" />
+ 
+    </a-space>
+    <!-- default-expand-all-rows -->
+    <a-table
+      class="table"
+      :columns="columns"
+      :data="refData.dataSource"
+      :pagination="false"
+      row-key="id"
+      :scroll="{ x: '120%' }"
+    >
+        <template #name="{ record }">
+          <span> {{ getParseLang(record.name, lang.getLang) }} </span>
+          <template v-if="record.permsArr && record.permsArr.length > 0">
+            <a-tag v-for="item in record.permsArr" :key="item.id" class="tag-name">{{ item.name }}</a-tag>
+          </template>
+        </template>
+        <template #refresh="{ record }">
+          <span v-if="['1', '2'].includes(record.type)">{{
+            record.refresh ? "是" : "否"
+          }}</span>
+        </template>
+        <template #icon="{ record }">
+          <!-- <a>123456789</a> -->
+          <span v-if="!record.icon"></span>
+          <svg-icon v-else class="icon" :icon="record.icon" />
+        </template>
+
+        <template #id="{ record }">
+          <a
+            href="javascript:;"
+            class="a-link"
+            style="margin-right: 1rem"
+            @click="evEdit(record)"
+            >编辑</a
+          >
+          <a-popconfirm
+            content="确认删除该信息?"
+            ok-text="确定"
+            cancel-text="取消"
+            @ok="evDelete(record.id)"
+          >
+            <a href="javascript:;" class="a-link">删除</a>
+        </a-popconfirm>
+        </template>
+    </a-table>
+
+    <a-modal
+      v-model:visible="refData.visible"
+      title="菜单信息"
+      :centered="true"
+      :width="700"
+      :footer="false"
+      simple
+    >
+      <div class="form-box">
+        <a-form
+          :model="refData.fromData"
+        
+          @submit-success="evHandleSubmit"
+        >
+          <a-form-item
+            field="name"
+            label="路由名称"
+            :rules="[{ required: true, message: '请输入路由名称' }]"
+            :validate-trigger="['change', 'input', 'blur']"
+          >
+            <!-- <a-input
+              v-model="refData.fromData.name"
+              placeholder="请输入路由名称"
+            /> -->
+
+            <MonacoEditor  v-model="refData.fromData.name"  />
+          </a-form-item>
+
+          <a-form-item
+            field="menu_id"
+            label="上级路由"
+            :validate-trigger="['change', 'input', 'blur']"
+          >
+            <a-tree-select
+              v-model="refData.fromData.menu_id"
+              placeholder="一级菜单"
+              :data="refData.menuTree"
+              :allow-clear="true"
+              :field-names="{
+                children: 'children',
+                title: 'name',
+                key: 'id',
+              }"
+              :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
+            />
+          </a-form-item>
+
+          <a-form-item
+            field="path"
+            label="路由标识"
+            :rules="[{ required: true, message: '请输入路由标识' }]"
+            :validate-trigger="['change', 'input', 'blur']"
+          >
+            <a-input
+              v-model="refData.fromData.path"
+              placeholder="请输入路由标识"
+            />
+          </a-form-item>
+
+          <a-form-item
+            v-if="!refData.fromData.menu_id"
+            field="icon"
+            label="路由图标"
+            :rules="[{ required: true, message: '请选择路由图标' }]"
+            :validate-trigger="['change', 'input', 'blur']"
+          >
+            <a-input
+              v-model="refData.fromData.icon"
+              placeholder="请设置菜单图标"
+            >
+              <template
+                v-if="refData.fromData.icon"
+                #suffix
+              >
+                <svg-icon class="input-icon" :icon="refData.fromData.icon" />
+              </template>
+              <template #append>
+                <icon-settings class="icon-append" @click="evIconModal" />
+                <!-- <setting-outlined class="icon-append" @click="evIconModal" /> -->
+              </template>
+            </a-input>
+          </a-form-item>
+
+          <a-form-item v-if="isUrlView" field="url" label="路由地址">
+            <!-- :rules="[{ required: true, message: '请输入路由名称' }]"
+            :validate-trigger="['change', 'input', 'blur']" -->
+<!--      
+            <a-select v-model:value="refData.fromData.url" placeholder="请选择路由地址">
+              <a-select-option v-for="item in refData.menuList" :key="item" :value="item"> {{  item  }}</a-select-option>
+            </a-select> -->
+
+            <a-select v-model="refData.fromData.url"  :options="refData.menuList" :field-names="{ value: 'item', label: 'item' }"  placeholder="请选择路由地址" />
+           
+          </a-form-item>
+
+
+          <a-form-item v-if="isUrlView" label="权限标识">
+            <div class="flex-col-body">
+              <a-form-item
+                v-for="(item, index) in refData.fromData.permsArr"
+                :key="index"
+              >
+                <a-input v-model="item.name" placeholder="名称" />
+                <a-input
+                  v-model="item.perms"
+                  style="margin-left: 1rem"
+                  placeholder="请输入权限标识"
+                />
+                <div class="flex-row-icon">
+                  <icon-close-circle-fill
+                    v-if="refData.fromData.permsArr.length > 1"
+                    @click="refData.fromData.permsArr.splice(index, 1)"
+                  />
+                  <icon-plus-circle-fill
+                    v-if="refData.fromData.permsArr.length - 1 === index"
+                    class="icon-plus-circle-fill"
+                    @click="
+                      refData.fromData.permsArr.push({
+                        menuName: '',
+                        perms: '',
+                      })
+                    "
+                  />
+                </div>
+              </a-form-item>
+            </div>
+          </a-form-item>
+
+
+          <a-form-item v-if="isUrlView" name="refresh" label="缓存">
+            <a-switch
+              v-model:checked="refData.fromData.refresh"
+              checked-value="1"
+              unchecked-value="0"
+            >
+              <template #checked> ON </template>
+              <template #unchecked> OFF </template>
+            </a-switch>
+          </a-form-item>
+
+          <a-form-item
+            name="sortNumber"
+            label="序号"
+            :rules="[{ required: true, message: '请输入序号' }]"
+            :validate-trigger="['change', 'input', 'blur']"
+          >
+            <a-input-number v-model="refData.fromData.sortNumber" :min="0" />
+          </a-form-item>
+
+          <a-form-item>
+            <div class="modal-footer">
+              <a-button type="primary" size="large" html-type="submit"
+                >确定</a-button
+              >
+              <a-button size="large" @click="evCancel"> 取消</a-button>
+            </div>
+          </a-form-item>
+        </a-form>
+      </div>
+    </a-modal>
+
+    <a-modal v-model:visible="refData.visibleIcon" simple :footer="false">
+      <div class="font-view">
+        <div
+          v-for="(item, index) in refData.iconfontArr"
+          :key="index"
+          class="icon-box"
+        >
+          <svg-icon
+            :icon="item.font_class"
+            :class="{
+              icon: true,
+              'icon-active': item.font_class === refData.fromData.icon,
+            }"
+            @click="evOnIconName(item.font_class)"
+          />
+        </div>
+      </div>
+    </a-modal>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, computed, ref, watch} from "vue";
+
+import { useRoute } from "vue-router";
+import { Message, Notification } from '@arco-design/web-vue'
+import MonacoEditor from "@/components/MonacoEditor/index.vue"
+import iconsvgJson from "@/assets/iconsvg/iconfont.json";
+import { useLangStore } from '@/store/modules/langStore'
+import {  getParseLang } from '@/utils'
+import _ from "lodash";
+import { columns } from './config'
+import { systemSetMenu, systemFinMenuAll, systemDeleteMenu, systemUpdateMenu } from "@/api/path/system.api"
+import { useSystemStore } from '@/store/modules/systemStore'
+
+const route = useRoute();
+const lang = useLangStore()
+const systemStore = useSystemStore()
+
+ 
+const refData = reactive({
+  visible: false,
+  visibleIcon: false,
+  dataSource: [],
+  fromData: {
+    name: "",
+    parentId: undefined,
+    menu_id: undefined,
+    type: "0",
+    url: "",
+    sortNumber: 99,
+    refresh: 0,
+    permsArr: [{ name: "", perms: "" }],
+  },
+  menuTree: [],
+  menuList: [],
+  iconfontArr: []
+});
+const menuTreeList = ref([])
+
+const isUrlView = computed(() => {
+  // if (refData.fromData.menu_id) {
+  //   const obj =  menuTreeList.value.find(item => item.id == refData.fromData.menu_id)
+  //   return refData.fromData.menu_id && ["1", "2"].includes(obj.type);
+  // }
+  // return false;
+  return true;
+});
+
+const fnTreeData = (data) => {
+  return data.map((item) => {
+    if (item.children && item.children.length > 0 && item.type !== "3") {
+      item.children = item.children.sort((a, b) => {
+        if (a.sortNumber === b.sortNumber) {
+          return a.id - b.id;
+        }
+        return a.sortNumber - b.sortNumber;
+      });
+      item.children = fnTreeData(item.children);
+    }
+    if (item.type === '2') {
+      item.permsArr = item.children;
+      Reflect.deleteProperty(item, "children");
+    }
+    return item;
+  });
+};
+
+const fnDeleteTreeSon = (data) => {
+  return data.filter((item) => {
+    item.name = getParseLang(item.name, lang.getLang)
+    Reflect.deleteProperty(item, 'icon')
+    if (item.children && item.children.length > 0) {
+      item.children = fnDeleteTreeSon(item.children);
+    }
+    return ["1"].includes(item.type);
+  });
+};
+
+const fnGetFormData = async () => {
+  const arr = [];
+  const comp = import.meta.glob("../../../views/**/index.vue");
+  if (comp) {
+    Object.keys(comp).forEach((key) => {
+      arr.push(`${comp[key]}`. replace(/^.*import\("\/src\/([^?"]+).*$/, '$1'));
+    });
+    arr.push("views/system/menu/index.vue")
+    refData.menuList = arr
+  }
+};
+
+const evCancel = () => {
+  refData.visibleIcon = false;
+  refData.visible = false;
+};
+
+const evInitData = async () => {
+  evCancel();
+  const { data } = await systemFinMenuAll();
+  const arr = fnTreeData(_.cloneDeep(data));
+  refData.dataSource = arr
+  refData.menuTree = fnDeleteTreeSon(_.cloneDeep(data));
+  menuTreeList.value = fnGetTreeList(_.cloneDeep(data))
+};
+
+
+const fnGetTreeList = (data) => {
+  return data.reduce((iter, val) => {
+    if (["1"].includes(val.type)) {
+      iter.push(val);
+    }
+    return val.children ? [...iter, ...fnGetTreeList(val.children)] : iter;
+  }, []);
+};
+
+const evEdit = async (data) => {
+  refData.fromData = data;
+  if(!data.permsArr || data.permsArr.length == 0){
+    refData.fromData.permsArr = [{ name: "", perms: "" }]
+  }
+  refData.visible = true;
+};
+
+const evHandleSubmit = async (values) => {
+  const data = JSON.parse(JSON.stringify(values));
+  data.type = "1"
+  if (data.url)  data.type = "2";
+  if(data.id){
+    await systemUpdateMenu(data)
+  }else{
+    await systemSetMenu(data);
+  }
+  evInitData();
+};
+
+const evDelete = async (id) => {
+  await systemDeleteMenu({ id });
+  evInitData();
+};
+
+const evAddMenu = async () => {
+  refData.fromData = {
+    name: `{
+  "zh-CN": "",
+  "en-US": "",
+  "th-TH": ""
+}`,
+    permsArr: [{ name: "", perms: "" }]
+  };
+  try {
+    await fnGetFormData();
+  } catch (e) {
+    //
+  }
+  refData.visible = true;
+};
+
+const evIconModal = async () => {
+  refData.iconfontArr = iconsvgJson.glyphs.filter((item) => {
+    return ["menu", "Dh"].includes(item.font_class.split("-")[0]);
+  });
+  refData.visibleIcon = true;
+};
+
+const evOnIconName = (item) => {
+  refData.fromData.icon = item;
+  refData.fromData = { ...refData.fromData };
+  refData.visibleIcon = false;
+  Message.success({
+    content: `选中  ${item}`,
+    duration: 2000,
+  });
+};
+
+// 上传
+const customRequest = async (option) => { 
+   const { file } = option.fileItem;
+  // 上传
+  const fileName = `thumbnail_${file.name}`
+  const key = `test/${fileName}`
+
+  const client = await systemStore.getSTSClient()
+  const resClient = await client.putObject({
+    key: key,
+    body: file
+  })
+  if (resClient.statusCode === 200) {
+    console.log('上传成功')
+  }
+
+};
+ 
+onMounted(() => {
+  evInitData()
+ 
+});
+</script>
+
+<style scoped lang="less">
+.head-title {
+  display: flex;
+  justify-content: space-between;
+  .fn-headTitleDiv();
+  padding: 0.5rem 0;
+}
+
+.table {
+  margin-top: 1.5rem;
+  .tag-name {
+    margin: 0.2rem;
+  }
+  .icon {
+    font-size: 20px;
+  }
+}
+
+.icon-append {
+  &:hover {
+    cursor: pointer;
+  }
+}
+
+:deep(.arco-col-19) {
+  flex: none !important;
+  width: 340px !important;
+}
+
+.form-box {
+  padding-left: 4rem;
+  .flex-col-body {
+    display: flex;
+    flex-direction: column;
+    .flex-row-icon {
+      min-width: 35px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-left: 10px;
+      .icon-plus-circle-fill {
+        font-size: 16px;
+        margin-left: 0.5rem;
+        // color: @orange_1;
+        &:hover {
+          cursor: pointer;
+          transform: scale(1.4);
+          // color: @blue_0;
+        }
+      }
+      svg:hover {
+        cursor: pointer;
+        transform: scale(1.4);
+        // color: @red_0;
+      }
+    }
+  }
+}
+.modal-footer {
+  width: 150px;
+  margin: 0 auto 16px auto;
+  display: flex;
+  justify-content: space-between;
+}
+
+.font-view {
+  display: flex;
+  flex-wrap: wrap;
+  .icon-box {
+    width: 30px;
+    height: 30px;
+    margin: 10px;
+    .icon {
+      width: 100%;
+      height: 100%;
+      font-size: 100px;
+      transform: scale(1, 1);
+      animation: mymove 1s;
+      animation-fill-mode: forwards;
+      color: #9696a0;
+      cursor: pointer;
+
+      &:hover,
+      &.icon-active {
+        background: #dbdbf3;
+        transform: scale(1, 1);
+        animation: mymove2 1s;
+        animation-fill-mode: forwards;
+      }
+    }
+  }
+}
+@keyframes mymove {
+  0% {
+    transform: scale(1, 1);
+  }
+  50% {
+    transform: scale(1.2, 1.2);
+  }
+  100% {
+    transform: scale(1.1, 1.1);
+  }
+}
+@keyframes mymove2 {
+  0% {
+    transform: scale(1, 1);
+  }
+  50% {
+    transform: scale(1.3, 1.3);
+  }
+  100% {
+    transform: scale(1.1, 1.1);
+  }
+}
+</style>

+ 24 - 0
src/views/system/role/config.js

@@ -0,0 +1,24 @@
+export const columns = [
+ 
+  { title: '用户ID', dataIndex: 'id', align: 'center', width:100   },
+  { title: '员工账号', dataIndex: 'username', align: 'center', width:100   },
+  { title: '用户工号', dataIndex: 'jobId', align: 'center' , width:100   },
+  { title: '用户姓名', dataIndex: 'name', align: 'center',  width:100   },
+  {
+    title: '用户状态',
+    dataIndex: 'state',
+    align: 'center',
+    slotName: 'state',
+    width:200 
+  },
+  { title: '更新时间', dataIndex: 'updatedAt', align: 'center', width:200 },
+  { title: '创建时间', dataIndex: 'createdAt', align: 'center', width:200 },
+  {
+    title: '操作',
+    dataIndex: 'id',
+    slotName: 'id',
+    align: 'center',
+    width: 180,
+    fixed: "right",
+  }
+]

+ 435 - 0
src/views/system/role/index.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="container">
+    <div class="head-title">
+      <span> {{ route.meta.title }} </span>
+      <div class="tip">
+        <span>
+          根据账号的职能为账号赋予角色属性,根据角色进行权限设置,点击可查看
+        </span>
+        <router-link :to="{ query: { go: 'oa/user/index' } }">
+          <a href="javascript:;">账号列表</a>
+        </router-link>
+      </div>
+    </div>
+
+    <div class="content">
+      <div class="content-block">
+        <div class="content-main">
+          <div
+            v-for="(item, index) in refData.defaultCard"
+            :key="index"
+            class="card-box"
+          >
+            <div class="card-head-right">
+              <a href="javascript:;" @click="evOpenModal(true, item)"
+                >修改权限</a
+              >
+            </div>
+
+            <div class="card-content">
+              <!-- <icon-role class="icon" /> -->
+              <svg-icon class="icon" icon="role" />
+              <div class="card-title">{{ item.name }}</div>
+              <div class="card-explain">
+                <span>{{ item.description }}</span>
+                <span style="margin-top: 0.4rem"
+                  >该角色目前已配置 {{ item.users.length || 0 }} 个账号</span
+                >
+              </div>
+            </div>
+
+            <div class="card-foot">
+              <div>
+                <span>角色权限</span>
+                <a-popover position="right">
+                  <a href="javascript:;">详情</a>
+                  <template #content>
+                    <a-tree
+                      v-if="item.children.length > 0"
+                      class="card-detail"
+                      :auto-expand-parent="true"
+                      :field-names="{
+                        children: 'children',
+                        title: 'name',
+                        key: 'id',
+                      }"
+                      :data="item.children"
+                    />
+                    <div v-else class="card-no-detail">暂无数据</div>
+                  </template>
+                </a-popover>
+              </div>
+              <div>
+                <span>角色账号</span>
+                <a href="javascript:;" @click="evOpenUser(item.id)">列表</a>
+              </div>
+            </div>
+          </div>
+          <div class="card-box">
+            <div class="card-content">
+              <icon-plus 
+                class="add-roles-icon"
+                @click="evOpenModal(true, undefined)"
+              />
+
+              <p>
+                配置自定义角色,并在该角色下配置员工账号,灵活管理员工账号权限
+              </p>
+              <a-button
+                type="outline"
+                class="plus-button"
+                @click="evOpenModal(true, undefined)"
+              >
+                添加角色
+              </a-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <a-modal
+      v-model:visible="refData.visible"
+      :width="1300"
+      simple
+      :footer="false"
+    >
+      <div class="operation-entry">
+        <div class="operation-search">
+          <a-input-search
+            style="margin: 0 0.6rem"
+            placeholder="请输入用户名称"
+            @press-enter="
+              (e) => {
+                evGetUserList(e.target._value);
+              }
+            "
+            @search="
+              (e) => {
+                evGetUserList(e);
+              }
+            "
+          />
+        </div>
+        <a-table
+          :columns="columns"
+          :data="refData.tableData"
+          :pagination="false"
+          :scroll="{ y: 350 }"
+        >
+          <!-- <template #state="{ record }">
+            <a-tag v-if="record.roleList && record.roleList.length > 0">{{
+              record.roleList[0]?.roleName || "-"
+            }}</a-tag>
+          </template> -->
+
+          <template #state="{ record }">
+            <a-popconfirm
+              :content="`确认要${record.state === 0 ? '禁用' : '开启'}${record.username}用户吗?`"
+              ok-text="确定"
+              cancel-text="取消"
+              @ok="evSwitchStatus(record)"
+            >
+              <div>
+                <a-switch v-model="record.state" style="pointer-events: none" :checked-value="1" :unchecked-value="0" />
+              </div>
+            </a-popconfirm>
+          </template>
+
+          <template #status="{ record }">
+            <a-switch
+              v-model="record.status"
+              disabled
+              style="pointer-events: none"
+              :checked-value="0"
+              :unchecked-value="1"
+            />
+          </template>
+          <template #id="{ record }">
+            <a-popconfirm
+              content="确认解绑该用户?"
+              ok-text="确定"
+              cancel-text="取消"
+              @ok="evDelete(record.id)"
+            >
+              <a class="a-link">解绑</a>
+            </a-popconfirm>
+          </template>
+        </a-table>
+      </div>
+      <!-- a-button(size="large" type="primary" @click="ev_delAdminRoleService") -->
+    </a-modal>
+
+    <modal-mode ref="refModalMode" @retrigger-init="evTriggerInit"></modal-mode>
+  </div>
+</template>
+
+<script setup>
+import _ from "lodash";
+import { onBeforeMount, reactive, ref } from "vue";
+import { useRoute } from "vue-router";
+import { Message } from '@arco-design/web-vue'
+import modalMode from "./modalMode.vue";
+import { systemFindRoleList, systemFindRoleOrUser, updateUserState, systemRelieveRoleUserById } from "@/api/path/system.api";
+import { useLangStore } from '@/store/modules/langStore'
+import { getParseLang } from '@/utils'
+
+import { columns } from './config'
+
+const refModalMode = ref();
+const langStore = useLangStore()
+const route = useRoute();
+const refData = reactive({
+  roleId: undefined,
+  defaultCard: [],
+  visible: false,
+  tableData: [],
+});
+
+const fnTreeFilter = (data, ids) => {
+  return data.filter((item) => {
+    Reflect.deleteProperty(item, "icon");
+    if (item.children && item.children.length > 0) {
+      item.children = fnTreeFilter(item.children, ids);
+    }
+    return ids.includes(item.id);
+  });
+};
+
+const evSwitchStatus = async (e) =>{
+  await updateUserState({
+    id:e.id,
+    state: e.state == 1 ? 0 : 1,
+  })
+  Message.success({
+    content: '操作成功',
+    duration: 2000,
+  })
+  evGetUserList()
+}
+
+// 获取user列表
+const evGetUserList = async (e) => {
+  const { data } = await systemFindRoleOrUser({
+    id: refData.roleId,
+    name: e,
+  });
+  refData.tableData = data;
+};
+const evOpenUser = async (id) => {
+  refData.roleId = id;
+  await evGetUserList();
+  refData.visible = true;
+};
+
+// 扁平菜单数据转换成树形
+const convertToTree = (data) => {
+  return data.reduce((acc, current) => {
+    const parent = acc.find((item) => item.id === current.menuId);
+    if (parent) {
+      if (!parent.children) {
+        parent.children = [];
+      }
+      
+      parent.children.push(current);
+    } else {
+      acc.push(current);
+    }
+    return acc;
+  }, []);
+};
+// 初始化数据
+const evInitData = async () => {
+  const { data } = await systemFindRoleList();
+  refData.defaultCard = data.map((item) => {
+    item.children = item.children.map((cItem) => {
+      cItem.name = getParseLang(cItem.name, langStore.getLang)
+      cItem.children = cItem.children.map((cItem) => {
+        cItem.name = getParseLang(cItem.name, langStore.getLang)
+        return cItem
+      })
+      return cItem
+    })
+    item.children = convertToTree(item.children);
+    return item;
+  });
+};
+
+// 解绑
+const evDelete = async (userId) => {
+  await systemRelieveRoleUserById({
+    id: refData.roleId,
+    userId: userId
+  });
+  Message.success({
+    content: "操作成功",
+    duration: 2000,
+  });
+  await evGetUserList();
+  evInitData();
+};
+
+const evOpenModal = (state, data) => {
+  refModalMode.value.modalSwitch(state, data);
+};
+
+const evTriggerInit = async (state) => {
+  evOpenModal(state, undefined);
+  await evInitData();
+};
+
+onBeforeMount(() => {
+  evInitData();
+});
+</script>
+
+<style scoped lang="less">
+.container {
+  padding: 0 1rem 1rem 1rem;
+}
+
+a {
+  // color: @blue_2;
+  text-decoration: none;
+  &:hover {
+    // color: @blue_0;
+    cursor: pointer;
+  }
+}
+
+.head-title {
+  .fn-headTitleDiv();
+
+  a {
+    color: #37f;
+    text-decoration: none;
+  }
+}
+
+.operations {
+  display: flex;
+}
+
+.content {
+  padding: 1rem 0 2rem 1rem;
+  display: flex;
+
+  .content-block {
+    display: block;
+    width: 100%;
+
+    .content-main {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 1.2rem;
+    }
+  }
+
+  .card-box {
+    position: relative;
+    font-size: 14px;
+    width: 16rem;
+    height: 22rem;
+    border-radius: 5px;
+    border: 1px solid @black_3;
+    padding: 20px;
+    display: flex;
+    transition: 0.3s;
+    flex-direction: column;
+    justify-content: space-between;
+    background: @bg_color_3;
+    box-shadow: 0 1px 3px @black_2;
+    transition: all 0.1s ease-in-out;
+
+    &:hover {
+      transform: scale(1.02);
+      box-shadow: 0 1px 20px @black_3;
+    }
+
+    .card-head-left {
+      position: absolute;
+      left: 10px;
+      top: 5px;
+      font-size: 20px;
+      opacity: 0.4;
+    }
+
+    .card-head-right {
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    .card-content {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      color: var(--main-font-color);
+
+      .icon {
+        width: 34px;
+        height: 34px;
+        font-size: 12px;
+      }
+
+      .card-explain {
+        display: flex;
+        flex-direction: column;
+        max-height: 120px;
+        overflow: hidden;
+        width: 100%;
+      }
+
+      .card-title {
+        color: var(--color-text-5);
+        font-weight: bold;
+        margin: 20px 0 30px 0;
+      }
+
+      .add-roles-icon {
+        font-size: 60px;
+        color: #c7c7c7;
+        margin: 50px 0;
+        cursor: pointer;
+      }
+
+      .plus-button {
+        margin-top: 3rem;
+        color: var(--color-text-5);
+        border: 1px solid @black_3;
+      }
+    }
+
+    .card-foot {
+      color: var(--main-font-color);
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+}
+
+.card-detail {
+  max-height: 300px;
+  min-width: 260px;
+  overflow-x: hidden;
+  overflow-y: auto;
+  overflow-y: overlay;
+}
+
+.operation-entry {
+  display: flex;
+  flex-direction: column;
+  margin: 1rem 0 4rem 0;
+
+  .operation-search {
+    display: flex;
+    justify-content: flex-end;
+    margin-bottom: 1.5rem;
+  }
+
+  .arco-input-wrapper {
+    width: 16rem;
+  }
+}
+</style>

+ 323 - 0
src/views/system/role/modalMode.vue

@@ -0,0 +1,323 @@
+、<template>
+  <div class="container">
+    <a-modal v-model:visible="refData.modalVisible" :width="1300" simple :footer="false">
+      <a-form
+        ref="formRef"
+        class="form-box"
+        :model="refData.form"
+        :label-col-props="{ span: 3 }"
+        :wrapper-col-props="{ span: 19 }"
+        @submit-success="handleSubmit"
+      >
+        <a-form-item
+          field="name"
+          label="角色名称"
+          :rules="[{ required: true, message: '请输入角色名称' }]"
+          :validate-trigger="['change', 'input', 'blur']"
+        >
+          <a-input v-if="refData.modalSate" v-model="refData.form.name" placeholder="请输入角色名称" />
+          <div v-else>
+            <span>{{ refData.form.name }}</span>
+            <a-popconfirm
+              v-if="refData.form.userNum === 0"
+              :content="`确定删除${refData.form.name}角色?`"
+              ok-text="确定"
+              cancel-text="取消"
+              @ok="evDeleteRole"
+            >
+              <a href="javascript:;" class="del-role"> 删除该角色</a>
+            </a-popconfirm>
+
+            <a-tooltip v-else content="请确认该角色下无账号">
+              <span class="del-role del-role-color"> 删除该角色</span>
+            </a-tooltip>
+          </div>
+        </a-form-item>
+
+        <a-form-item
+          field="cacheTreeKeys"
+          label="选择权限"
+          :validate-trigger="['change', 'input', 'blur']"
+          :rules="[{ required: true, message: '请选择权限' }]"
+        >
+          <a-tree
+            v-if="refData.treeData.length > 0"
+            v-model:checked-keys="refData.form.cacheTreeKeys"
+            :auto-expand-parent="true"
+            :field-names="{
+              children: 'children',
+              title: 'name',
+              key: 'id',
+            }"
+            class="option-permissions"
+            :checkable="true"
+            :data="refData.treeData"
+            checked-strategy="all"
+            @check="evOnCheck"
+          />
+          <a-list class="option-permissions-list" :data="refData.treeList">
+            <template #header> 已选权限</template>
+            <template #item="{ item, index }">
+              <a-list-item :key="index">
+                <span>{{ getParseLang(item.name, langStore.getLang) }}</span>
+                <a-tag v-for="cItem in item.children" :key="cItem.id" class="role-tag">
+                  {{ cItem.name }}
+                </a-tag>
+              </a-list-item>
+            </template>
+          </a-list>
+        </a-form-item>
+
+        <a-form-item
+          field="description"
+          label="权限说明"
+          :rules="[{ required: true, message: '请输入权限说明' }]"
+          :validate-trigger="['change', 'input', 'blur']"
+        >
+          <a-textarea
+            v-model="refData.form.description"
+            placeholder="请输入权限说明"
+            :rows="4"
+            maxlength="200"
+            :auto-size="{ minRows: 4, maxRows: 4 }"
+          />
+        </a-form-item>
+
+        <a-form-item>
+          <div class="modal-footer">
+            <a-button type="primary" size="large" html-type="submit">确定</a-button>
+            <a-button size="large" @click="modalSwitch(false, undefined)"> 取消</a-button>
+          </div>
+        </a-form-item>
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script  setup>
+import { nextTick, reactive } from 'vue'
+import { Message } from '@arco-design/web-vue'
+import { systemFinMenuAll, systemSetRole, systemUpdateRole, systemDeleteRole } from "@/api/path/system.api"
+import _ from 'lodash'
+import {  getParseLang } from '@/utils'
+import { useLangStore } from '@/store/modules/langStore'
+ 
+const emit = defineEmits(['retriggerInit'])
+const langStore = useLangStore()
+const refData = reactive({
+  modalSate: false,
+  modalVisible: false,
+  treeFlattenData: [],
+  treeData: [],
+  form: {
+    id: undefined,
+    name: '',
+    description: '权限说明:',
+    checkedKeys: [],
+    cacheTreeKeys: [],
+    userNum: 0,
+  },
+  treeList: [],
+})
+// tree key拼接上下级关系
+const fnTreeData = (data, ids = '') => {
+  return data.map((item) => {
+    Reflect.deleteProperty(item, 'icon')
+    item.name = getParseLang(item.name, langStore.getLang)
+    if (item.children && item.children.length > 0) {
+      item.children = fnTreeData(item.children, item.key)
+    }
+    return item
+  })
+}
+// 扁平化树形数据
+const fnFlatten = (data) => {
+  return data.reduce((iter, val) => {
+    if (["2","3"].includes(val.type)) {
+      iter.push(val)
+    }
+    return val.children ? [...iter, ...fnFlatten(val.children)] : iter
+  }, [])
+}
+// 数据 tree
+const fnTerrify = (data) => {
+  // 使用一个对象来存储每个节点的引用,以便快速查找父节点
+  const nodeMap = {};
+  // 第一次遍历,将每个节点添加到 nodeMap 中
+  data.forEach(item => {
+    nodeMap[item.id] = { ...item, children: [] };
+  });
+
+  // 第二次遍历,构建树形结构
+  const tree = [];
+  data.forEach(item => {
+    if (item.menuId !== null && nodeMap[item.menuId]) {
+      nodeMap[item.menuId].children.push(nodeMap[item.id]);
+    } else {
+      tree.push(nodeMap[item.id]);
+    }
+  });
+
+  return tree;
+};
+ 
+
+
+// 勾选权限
+const evOnCheck = (checkedKeys,info) => {
+  let treeData = checkedKeys
+  if(info) treeData = checkedKeys.concat(info.halfCheckedKeys)
+  const arr = refData.treeFlattenData.filter(item =>{
+    return treeData.includes(item.id)
+  })
+  refData.form.checkedKeys = treeData
+  refData.treeList = fnTerrify(arr)
+}
+
+// 获取tree 层级key
+const fnFlattenKey = (data) => {
+  if(!data) return []
+  return data.reduce((iter, val) => {
+    if(["2","3"].includes(val.type) && !val.children){
+      iter.push(val.id)
+    }
+    return val.children ? [...iter, ...fnFlattenKey(val.children)] : iter
+  }, [])
+}
+
+
+const findTreeIds = (data) =>{
+  if(!data) return []
+  return data.reduce((iter, val) =>{
+    
+    iter.push(val.id)
+    return val.children && val.children.length >0 ? [...iter, ...findTreeIds(val.children)] : iter
+  }, [])
+}
+
+ 
+// modal 切换
+const modalSwitch = async (state, modular) => {
+  refData.treeList = []
+  
+  if (!state) refData.modalVisible = state
+  const { data } = await systemFinMenuAll()
+  refData.treeData =  fnTreeData(data) 
+  refData.treeFlattenData = fnFlatten(data)
+  if (modular) {
+    refData.modalSate = false 
+    const checkData =  findTreeIds(_.cloneDeep(modular.children))
+    const checky = fnFlattenKey(_.cloneDeep(modular.children))
+    refData.form = {
+      id: modular.id,
+      name: modular.name,
+      description: modular.description,
+      checkedKeys: checkData,
+      cacheTreeKeys:checky,
+      userNum:  0//modular.userNum,
+    }
+
+    evOnCheck(checkData)
+  } else {
+    refData.form = {
+      id: undefined,
+      name: '',
+      description: '权限说明:',
+      checkedKeys: [],
+      userNum: 0,
+    }
+    refData.modalSate = true
+  }
+  refData.modalVisible = state
+}
+
+// 删除角色
+const evDeleteRole = async () => {
+  await systemDeleteRole({ id: refData.form.id})
+  Message.success({
+    content: '删除成功',
+    duration: 2000,
+  })
+  emit('retriggerInit', false)
+}
+
+// 提交权限
+const handleSubmit = async (values) => {
+  const param = values
+  if (refData.modalSate) {
+    await systemSetRole(param)
+    Message.success({
+      content: '添加成功',
+      duration: 2000,
+    })
+  } else {
+    await systemUpdateRole(param)
+    Message.success({
+      content: '修改成功',
+      duration: 2000,
+    })
+  }
+  emit('retriggerInit', false)
+}
+defineExpose({
+  modalSwitch,
+  handleSubmit,
+})
+</script>
+
+<style lang="less" scoped>
+.form-box {
+  margin-top: 1rem;
+  .del-role {
+    position: absolute;
+    right: 8rem;
+  }
+  .del-role-color {
+    cursor: pointer;
+    color: var(--color-text-4);
+  }
+  a {
+    color: @blue_0;
+    &:hover {
+      color: @blue_2;
+      cursor: pointer;
+    }
+  }
+}
+.option-permissions {
+  padding-left: 1rem;
+  border: 1px solid @black_3;
+  display: 'inline-block';
+  width: 100%;
+  height: 400px;
+  border-radius: var(--border-radius-medium);
+  overflow-y: auto;
+}
+.option-permissions-list {
+  margin-left: 1rem;
+  width: 100%;
+  .role-tag {
+    margin-left: 0.5rem;
+  }
+  :deep(.arco-list-item-content) {
+    display: flex;
+    align-items: center;
+  }
+  :deep(.arco-list-header) {
+    font-weight: bold;
+    background-color: var(--color-neutral-2);
+    position: sticky;
+    top: 0;
+    z-index: 9;
+  }
+  :deep(.arco-list-bordered) {
+    height: 400px;
+  }
+}
+.modal-footer {
+  width: 200px;
+  margin: 0 auto 16px auto;
+  display: flex;
+  justify-content: space-around;
+}
+</style>

Some files were not shown because too many files changed in this diff