├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── README.md ├── auto-imports.d.ts ├── commitlint.config.js ├── components.d.ts ├── index.html ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ └── login.js ├── assets │ ├── iconfont │ │ ├── iconfont.scss │ │ └── iconfont.ttf │ ├── images │ │ ├── 403.png │ │ ├── 404.png │ │ ├── 500.png │ │ ├── iphone.png │ │ ├── logo.svg │ │ ├── money.png │ │ ├── msg01.png │ │ ├── msg02.png │ │ ├── msg03.png │ │ ├── msg04.png │ │ ├── msg05.png │ │ ├── notData.png │ │ ├── pie.png │ │ ├── see.png │ │ ├── suotou.svg │ │ └── upload.png │ ├── json │ │ ├── authButton.json │ │ └── routerList.json │ ├── md │ │ ├── 1675495963149.png │ │ ├── 30747d0cec5ee8bd5687c7cd8ba5c14.png │ │ ├── 4645021975f6b673cabc4ac71dc4bed.png │ │ └── dc155c038e20db06cbb341bfc4ebfc0.png │ └── svg │ │ ├── pifu.svg │ │ ├── quanping.svg │ │ ├── quxiaoquanping.svg │ │ ├── sosuo.svg │ │ ├── suotou.svg │ │ └── zhongyingwen.svg ├── components │ ├── CountTo │ │ ├── index.js │ │ └── src │ │ │ └── CountTo.vue │ ├── ErrorMessage │ │ ├── 403.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ └── index.scss │ ├── Loading │ │ ├── index.scss │ │ ├── index.vue │ │ ├── loading-01.vue │ │ ├── loading-02.vue │ │ ├── loading-03.vue │ │ ├── loading-04.vue │ │ ├── loading-05.vue │ │ ├── loading-06.vue │ │ ├── loading-07.vue │ │ ├── loading-08.vue │ │ ├── loading-09.vue │ │ ├── loading-10.vue │ │ ├── loading-11.vue │ │ ├── loading-12.vue │ │ ├── loading-13.vue │ │ ├── loading-14.vue │ │ ├── loading-15.vue │ │ ├── loading-16.vue │ │ ├── loading-17.vue │ │ └── loading-18.vue │ ├── LockScreen │ │ ├── index.js │ │ └── src │ │ │ ├── LockScreen.vue │ │ │ └── index.js │ ├── Message │ │ ├── index.js │ │ └── src │ │ │ ├── Message.vue │ │ │ ├── image │ │ │ ├── error.png │ │ │ ├── success.png │ │ │ └── warning.png │ │ │ └── types.js │ ├── MessageBox │ │ ├── index.js │ │ └── src │ │ │ ├── MessageBox.vue │ │ │ ├── image │ │ │ └── close.png │ │ │ └── index.js │ ├── WangEditor │ │ └── index.vue │ └── Webpage │ │ └── index.vue ├── config │ ├── config.js │ ├── nprogress.js │ └── piniaPersist.js ├── directives │ ├── index.js │ └── src │ │ ├── authButton.js │ │ ├── copy.js │ │ ├── debounce.js │ │ ├── throttle.js │ │ └── waterMarker.js ├── hooks │ ├── useAuthButtons.js │ ├── useEcharts.js │ └── useTheme.js ├── languages │ ├── index.js │ └── modules │ │ ├── en.js │ │ └── zh.js ├── layouts │ ├── LayoutClassic │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutColumns │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutTransverse │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutVertical │ │ ├── index.scss │ │ └── index.vue │ ├── components │ │ ├── Footer │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── Header │ │ │ ├── ToolBarLeft.vue │ │ │ ├── ToolBarRight.vue │ │ │ └── components │ │ │ │ ├── Avatar.vue │ │ │ │ ├── Breadcrumb.vue │ │ │ │ ├── CollapseIcon.vue │ │ │ │ ├── ColorPicker.vue │ │ │ │ ├── FullScreen.vue │ │ │ │ ├── Language.vue │ │ │ │ ├── LockScreen.vue │ │ │ │ ├── SearchMenu.vue │ │ │ │ └── ThemeSetting.vue │ │ ├── Main │ │ │ ├── components │ │ │ │ └── Maximize.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── Menu │ │ │ └── SubMenu.vue │ │ ├── Tabs │ │ │ ├── components │ │ │ │ └── MoreButton.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── ThemeDrawer │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── index.vue │ └── indexAsync.vue ├── main.js ├── mock │ └── index.js ├── plugins │ ├── echarts.js │ └── element-icon.js ├── routers │ ├── index.js │ └── modules │ │ ├── dynamicRouter.js │ │ └── staticRouter.js ├── stores │ ├── index.js │ └── modules │ │ ├── auth.js │ │ ├── keepAlive.js │ │ └── tabs.js ├── styles │ ├── common.scss │ ├── element-dark.scss │ ├── element.scss │ ├── reset.scss │ └── var.scss ├── utils │ ├── errorHandler.js │ ├── getEnv.ts │ ├── is.js │ ├── mittBus.js │ ├── service.js │ ├── tool.js │ └── util.js └── views │ ├── assembly │ ├── count.vue │ ├── drag.vue │ ├── guide.vue │ ├── loading.vue │ ├── message.vue │ ├── wangEditor.vue │ └── webpage.vue │ ├── auth │ ├── authButton.vue │ └── authMenu.vue │ ├── dataView │ ├── assets │ │ ├── china.json │ │ └── region.js │ ├── components │ │ └── MapChian.vue │ └── index.vue │ ├── directives │ ├── copy.vue │ ├── debounce.vue │ ├── throttle.vue │ └── watermark.vue │ ├── home │ ├── components │ │ ├── HomeCount.vue │ │ ├── HomeLine.vue │ │ ├── HomeTable.vue │ │ └── HomeUser.vue │ └── index.vue │ ├── language │ └── index.vue │ ├── login │ ├── components │ │ └── LoginForm.vue │ └── index.vue │ ├── menu │ ├── menu1 │ │ └── index.vue │ └── menu2 │ │ ├── menu2-1 │ │ └── index.vue │ │ └── menu2-2 │ │ ├── menu2-2-1 │ │ └── index.vue │ │ └── menu2-2-2 │ │ └── index.vue │ ├── panel │ ├── components │ │ ├── PancePie.vue │ │ ├── PancelLine.vue │ │ ├── PancelPictorialBar.vue │ │ └── progressBar.vue │ └── index.vue │ └── work │ ├── excel.vue │ ├── flowChart.vue │ └── print.vue ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # @see: http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] # 表示所有文件适用 6 | charset = utf-8 # 设置文件字符集为 utf-8 7 | end_of_line = lf # 控制换行类型(lf | cr | crlf) 8 | insert_final_newline = true # 始终在文件末尾插入一个新行 9 | indent_style = tab # 缩进风格(tab | space) 10 | indent_size = 2 # 缩进大小 11 | max_line_length = 130 # 最大行长度 12 | 13 | [*.md] # 表示仅 md 文件适用以下规则 14 | max_line_length = off # 关闭最大行长度限制 15 | trim_trailing_whitespace = false # 关闭末尾空格修剪 16 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # title 2 | VITE_GLOB_APP_TITLE = 'vue-diverse-admin' 3 | 4 | # port 5 | VITE_PORT = 3301 6 | 7 | # open 运行 npm run dev 时自动打开浏览器 8 | VITE_OPEN = true 9 | 10 | # 是否生成包分析文件 11 | VITE_REPORT = false 12 | 13 | # 是否开启gzip压缩 14 | VITE_BUILD_GZIP = false 15 | 16 | # 是否删除生产环境 console 17 | VITE_DROP_CONSOLE = true 18 | 19 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | NODE_ENV = 'development' 3 | 4 | # 本地环境接口地址 5 | VITE_API_URL = 'http://localhost:3301' -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境 2 | NODE_ENV = "production" 3 | 4 | # 线上环境接口地址 5 | VITE_API_URL = "http://localhost:3301" 6 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # 测试环境 2 | NODE_ENV = "test" 3 | 4 | # 测试环境接口地址(easymock) 5 | VITE_API_URL = "http://localhost:3301" 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | .eslintrc.js 15 | .prettierrc.js 16 | /src/mock/* 17 | 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @see: http://eslint.cn 2 | 3 | module.exports = { 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true 9 | }, 10 | /* 指定如何解析语法 */ 11 | parser: "vue-eslint-parser", 12 | /* 优先级低于 parse 的语法解析配置 */ 13 | parserOptions: { 14 | parser: "@typescript-eslint/parser", 15 | ecmaVersion: 2020, 16 | sourceType: "module", 17 | jsxPragma: "React", 18 | ecmaFeatures: { 19 | jsx: true 20 | } 21 | }, 22 | /* 继承某些已有的规则 */ 23 | extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:prettier/recommended"], 24 | /* 25 | * "off" 或 0 ==> 关闭规则 26 | * "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行) 27 | * "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错) 28 | */ 29 | rules: { 30 | // eslint (http://eslint.cn/docs/rules) 31 | "no-var": "error", // 要求使用 let 或 const 而不是 var 32 | "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行 33 | "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们 34 | "prefer-const": "off", // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const 35 | "no-irregular-whitespace": "off", // 禁止不规则的空白 36 | 37 | // typeScript (https://typescript-eslint.io/rules) 38 | "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量 39 | "@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore 40 | "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长 41 | "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。 42 | "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型 43 | "@typescript-eslint/ban-types": "off", // 禁止使用特定类型 44 | "@typescript-eslint/explicit-function-return-type": "off", // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明 45 | "@typescript-eslint/no-var-requires": "off", // 不允许在 import 语句中使用 require 语句 46 | "@typescript-eslint/no-empty-function": "off", // 禁止空函数 47 | "@typescript-eslint/no-use-before-define": "off", // 禁止在变量定义之前使用它们 48 | "@typescript-eslint/ban-ts-comment": "off", // 禁止 @ts- 使用注释或要求在指令后进行描述 49 | "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!) 50 | "@typescript-eslint/explicit-module-boundary-types": "off", // 要求导出函数和类的公共类方法的显式返回和参数类型 51 | 52 | // vue (https://eslint.vuejs.org/rules) 53 | "vue/no-v-html": "off", // 禁止使用 v-html 54 | "vue/script-setup-uses-vars": "error", // 防止 33 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import service from "@/utils/service.js"; 2 | 3 | export const routerList = () => { 4 | return service({ 5 | method: "GET", 6 | url: "/api/router/list" 7 | }); 8 | }; 9 | 10 | export const pageLogin = () => { 11 | return service({ 12 | method: "GET", 13 | url: "/api/login" 14 | }); 15 | }; 16 | 17 | export const getAuthButton = () => { 18 | return service({ 19 | method: "GET", 20 | url: "/api/auth/button" 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: iconfont; /* Project id 2667653 */ 3 | src: url("iconfont.ttf?t=1663324025864") format("truetype"); 4 | } 5 | .iconfont { 6 | font-family: iconfont !important; 7 | font-size: 20px; 8 | font-style: normal; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | cursor: pointer; 12 | } 13 | .icon-xiaoxi::before { 14 | font-size: 21.2px; 15 | content: "\e61f"; 16 | } 17 | .icon-zhuti::before { 18 | font-size: 22.4px; 19 | content: "\e638"; 20 | } 21 | .icon-sousuo::before { 22 | content: "\e611"; 23 | } 24 | .icon-contentright::before { 25 | content: "\e8c9"; 26 | } 27 | .icon-contentleft::before { 28 | content: "\e8ca"; 29 | } 30 | .icon-fangda::before { 31 | content: "\e826"; 32 | } 33 | .icon-suoxiao::before { 34 | content: "\e641"; 35 | } 36 | .icon-zhongyingwen::before { 37 | content: "\e8cb"; 38 | } 39 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/images/403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/403.png -------------------------------------------------------------------------------- /src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/404.png -------------------------------------------------------------------------------- /src/assets/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/500.png -------------------------------------------------------------------------------- /src/assets/images/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/iphone.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/images/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/money.png -------------------------------------------------------------------------------- /src/assets/images/msg01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/msg01.png -------------------------------------------------------------------------------- /src/assets/images/msg02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/msg02.png -------------------------------------------------------------------------------- /src/assets/images/msg03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/msg03.png -------------------------------------------------------------------------------- /src/assets/images/msg04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/msg04.png -------------------------------------------------------------------------------- /src/assets/images/msg05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/msg05.png -------------------------------------------------------------------------------- /src/assets/images/notData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/notData.png -------------------------------------------------------------------------------- /src/assets/images/pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/pie.png -------------------------------------------------------------------------------- /src/assets/images/see.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/see.png -------------------------------------------------------------------------------- /src/assets/images/suotou.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/images/upload.png -------------------------------------------------------------------------------- /src/assets/json/authButton.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": { 4 | "authButton": ["create", "remove"] 5 | }, 6 | "msg": "成功" 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/md/1675495963149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/md/1675495963149.png -------------------------------------------------------------------------------- /src/assets/md/30747d0cec5ee8bd5687c7cd8ba5c14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/md/30747d0cec5ee8bd5687c7cd8ba5c14.png -------------------------------------------------------------------------------- /src/assets/md/4645021975f6b673cabc4ac71dc4bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/md/4645021975f6b673cabc4ac71dc4bed.png -------------------------------------------------------------------------------- /src/assets/md/dc155c038e20db06cbb341bfc4ebfc0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/assets/md/dc155c038e20db06cbb341bfc4ebfc0.png -------------------------------------------------------------------------------- /src/assets/svg/pifu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/quanping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/quxiaoquanping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/sosuo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/suotou.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/zhongyingwen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/CountTo/index.js: -------------------------------------------------------------------------------- 1 | // import { withInstall } from '@/utils'; 2 | import CountTo from "./src/CountTo.vue"; 3 | 4 | export default CountTo; 5 | -------------------------------------------------------------------------------- /src/components/CountTo/src/CountTo.vue: -------------------------------------------------------------------------------- 1 | 6 | 120 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/403.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/404.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/500.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/index.scss: -------------------------------------------------------------------------------- 1 | .not-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | .not-img { 8 | margin-right: 120px; 9 | } 10 | .not-detail { 11 | display: flex; 12 | flex-direction: column; 13 | h2, 14 | h4 { 15 | padding: 0; 16 | margin: 0; 17 | } 18 | h2 { 19 | font-size: 60px; 20 | color: var(--el-text-color-primary); 21 | } 22 | h4 { 23 | margin: 30px 0 20px; 24 | font-size: 19px; 25 | font-weight: normal; 26 | color: var(--el-text-color-regular); 27 | } 28 | .el-button { 29 | width: 100px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Loading/index.scss: -------------------------------------------------------------------------------- 1 | .loading-box { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | width: 100%; 7 | height: 100%; 8 | .loading-wrap { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | padding: 98px; 13 | } 14 | } 15 | .dot { 16 | position: relative; 17 | box-sizing: border-box; 18 | display: inline-block; 19 | width: 32px; 20 | height: 32px; 21 | font-size: 32px; 22 | transform: rotate(45deg); 23 | animation: ant-rotate 1.2s infinite linear; 24 | } 25 | .dot i { 26 | position: absolute; 27 | display: block; 28 | width: 14px; 29 | height: 14px; 30 | background-color: var(--el-color-primary); 31 | border-radius: 100%; 32 | opacity: 0.3; 33 | transform: scale(0.75); 34 | transform-origin: 50% 50%; 35 | animation: ant-spin-move 1s infinite linear alternate; 36 | } 37 | .dot i:nth-child(1) { 38 | top: 0; 39 | left: 0; 40 | } 41 | .dot i:nth-child(2) { 42 | top: 0; 43 | right: 0; 44 | animation-delay: 0.4s; 45 | } 46 | .dot i:nth-child(3) { 47 | right: 0; 48 | bottom: 0; 49 | animation-delay: 0.8s; 50 | } 51 | .dot i:nth-child(4) { 52 | bottom: 0; 53 | left: 0; 54 | animation-delay: 1.2s; 55 | } 56 | @keyframes ant-rotate { 57 | to { 58 | transform: rotate(405deg); 59 | } 60 | } 61 | @keyframes ant-spin-move { 62 | to { 63 | opacity: 1; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/components/Loading/loading-02.vue: -------------------------------------------------------------------------------- 1 | 8 | 111 | -------------------------------------------------------------------------------- /src/components/Loading/loading-03.vue: -------------------------------------------------------------------------------- 1 | 4 | 75 | -------------------------------------------------------------------------------- /src/components/Loading/loading-04.vue: -------------------------------------------------------------------------------- 1 | 9 | 53 | -------------------------------------------------------------------------------- /src/components/Loading/loading-05.vue: -------------------------------------------------------------------------------- 1 | 4 | 44 | -------------------------------------------------------------------------------- /src/components/Loading/loading-06.vue: -------------------------------------------------------------------------------- 1 | 4 | 41 | -------------------------------------------------------------------------------- /src/components/Loading/loading-07.vue: -------------------------------------------------------------------------------- 1 | 4 | 42 | -------------------------------------------------------------------------------- /src/components/Loading/loading-08.vue: -------------------------------------------------------------------------------- 1 | 10 | 130 | -------------------------------------------------------------------------------- /src/components/Loading/loading-09.vue: -------------------------------------------------------------------------------- 1 | 6 | 73 | -------------------------------------------------------------------------------- /src/components/Loading/loading-10.vue: -------------------------------------------------------------------------------- 1 | 4 | 89 | -------------------------------------------------------------------------------- /src/components/Loading/loading-11.vue: -------------------------------------------------------------------------------- 1 | 13 | 93 | -------------------------------------------------------------------------------- /src/components/Loading/loading-12.vue: -------------------------------------------------------------------------------- 1 | 4 | 61 | -------------------------------------------------------------------------------- /src/components/Loading/loading-14.vue: -------------------------------------------------------------------------------- 1 | 7 | 68 | -------------------------------------------------------------------------------- /src/components/Loading/loading-15.vue: -------------------------------------------------------------------------------- 1 | 24 | 42 | -------------------------------------------------------------------------------- /src/components/Loading/loading-16.vue: -------------------------------------------------------------------------------- 1 | 4 | 70 | -------------------------------------------------------------------------------- /src/components/Loading/loading-17.vue: -------------------------------------------------------------------------------- 1 | 6 | 88 | -------------------------------------------------------------------------------- /src/components/LockScreen/index.js: -------------------------------------------------------------------------------- 1 | import LockScreen from "./src/index"; 2 | 3 | export { LockScreen }; 4 | -------------------------------------------------------------------------------- /src/components/LockScreen/src/LockScreen.vue: -------------------------------------------------------------------------------- 1 | 24 | 63 | 139 | -------------------------------------------------------------------------------- /src/components/LockScreen/src/index.js: -------------------------------------------------------------------------------- 1 | import LockScreenComponent from "./LockScreen.vue"; 2 | import { createApp, watch } from "vue"; 3 | const LockScreen = options => { 4 | const LockScreenApp = createApp(LockScreenComponent, options); 5 | showLockScreen(LockScreenApp); 6 | }; 7 | const showLockScreen = app => { 8 | const oFragment = document.createDocumentFragment(); 9 | const vm = app.mount(oFragment); 10 | document.body.appendChild(oFragment); 11 | vm.setVisible(true); 12 | watch(vm.state, state => { 13 | if (!state.visible) { 14 | hideMessageBox(app); 15 | } 16 | }); 17 | }; 18 | const hideMessageBox = app => { 19 | app.unmount(); 20 | }; 21 | export default LockScreen; 22 | -------------------------------------------------------------------------------- /src/components/Message/index.js: -------------------------------------------------------------------------------- 1 | import types from "./src/types"; 2 | import MessageComponent from "./src/Message.vue"; 3 | import { createApp, ref, watch } from "vue"; 4 | 5 | const Message = options => { 6 | const messageApp = createApp(MessageComponent, options); 7 | showMessage(messageApp, options.duration); 8 | }; 9 | 10 | Object.values(types).forEach(type => { 11 | Message[type] = options => { 12 | options.type = type; 13 | return Message(options); 14 | }; 15 | }); 16 | 17 | const messageArr = ref([]); 18 | 19 | function showMessage(app, duration) { 20 | const oFrag = document.createDocumentFragment(); 21 | const vm = app.mount(oFrag); 22 | messageArr.value.push(vm); 23 | document.body.appendChild(oFrag); 24 | setTop(vm); 25 | vm.setVisible(true); 26 | watch(messageArr, () => { 27 | setTop(vm); 28 | }); 29 | hideMessage(app, vm, duration); 30 | } 31 | 32 | function hideMessage(app, vm, duration) { 33 | vm.time = setTimeout(async () => { 34 | await vm.setVisible(false); 35 | app.unmount(); 36 | messageArr.value = messageArr.value.filter(item => item !== vm); 37 | clearTimeout(vm.time); 38 | vm.time = null; 39 | }, duration || 2000); 40 | } 41 | 42 | function setTop(vm) { 43 | const { setTop, height, margin } = vm; 44 | const currentIndex = messageArr.value.findIndex(item => item === vm); 45 | setTop(margin * (currentIndex + 1) + height * currentIndex); 46 | } 47 | 48 | export default Message; 49 | -------------------------------------------------------------------------------- /src/components/Message/src/Message.vue: -------------------------------------------------------------------------------- 1 | 13 | 58 | 98 | -------------------------------------------------------------------------------- /src/components/Message/src/image/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/components/Message/src/image/error.png -------------------------------------------------------------------------------- /src/components/Message/src/image/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/components/Message/src/image/success.png -------------------------------------------------------------------------------- /src/components/Message/src/image/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/components/Message/src/image/warning.png -------------------------------------------------------------------------------- /src/components/Message/src/types.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SUCCESS: "success", 3 | WARNING: "warning", 4 | MESSAGE: "message", 5 | ERROR: "error" 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/MessageBox/index.js: -------------------------------------------------------------------------------- 1 | import MessageBox from "./src/index"; 2 | 3 | export { MessageBox }; 4 | -------------------------------------------------------------------------------- /src/components/MessageBox/src/image/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1164095457/vue-diverse-admin/1d3eaa2c656fbae29353b57c943f02532ea4f876/src/components/MessageBox/src/image/close.png -------------------------------------------------------------------------------- /src/components/MessageBox/src/index.js: -------------------------------------------------------------------------------- 1 | import MessageBoxComponent from "./MessageBox.vue"; 2 | import { createApp, watch } from "vue"; 3 | export const fields = ["confirm", "prompt"]; 4 | const MessageBox = options => { 5 | const messageBoxApp = createApp(MessageBoxComponent, options); 6 | return new Promise((resolve, reject) => { 7 | showMessageBox(messageBoxApp, { resolve, reject }); 8 | }); 9 | }; 10 | fields.forEach(field => { 11 | MessageBox[field] = options => { 12 | options.field = field; 13 | return MessageBox(options); 14 | }; 15 | }); 16 | const showMessageBox = (app, { resolve, reject }) => { 17 | const oFragment = document.createDocumentFragment(); 18 | const vm = app.mount(oFragment); 19 | document.body.appendChild(oFragment); 20 | vm.setVisible(true); 21 | 22 | watch(vm.state, state => { 23 | if (!state.visible) { 24 | switch (state.type) { 25 | case "cancel": 26 | reject(); 27 | break; 28 | case "confirm": 29 | resolve(state.promptValue); 30 | break; 31 | default: 32 | break; 33 | } 34 | hideMessageBox(app); 35 | } 36 | }); 37 | }; 38 | 39 | const hideMessageBox = app => { 40 | app.unmount(); 41 | }; 42 | 43 | export default MessageBox; 44 | -------------------------------------------------------------------------------- /src/components/WangEditor/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/components/Webpage/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | // 项目名称 2 | export const APP_NAME = "vue-diverse-admin"; 3 | // 首页地址(默认) 4 | export const HOME_URL = "/home/index"; 5 | 6 | // 登录页地址(默认) 7 | export const LOGIN_URL = "/login"; 8 | 9 | // 默认主题颜色 10 | export const DEFAULT_PRIMARY = "#409EFF"; 11 | 12 | // Tabs(白名单地址,不需要添加到 tabs 的路由地址) 13 | export const TABS_WHITE_LIST = ["/403", "/404", "/500", LOGIN_URL]; 14 | 15 | export const ROUTER_WHITE_LIST = ["/500"]; 16 | -------------------------------------------------------------------------------- /src/config/nprogress.js: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress"; 2 | import "nprogress/nprogress.css"; 3 | 4 | NProgress.configure({ 5 | easing: "ease", // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }); 11 | export default NProgress; 12 | -------------------------------------------------------------------------------- /src/config/piniaPersist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description pinia持久化参数配置 3 | * @param {String} key 存储到持久化的 name 4 | * @param {Array} paths 需要持久化的 state name 5 | * @return persist 6 | * */ 7 | const piniaPersistConfig = (key, paths) => { 8 | const persist = { 9 | key, 10 | storage: window.localStorage, 11 | // storage: window.sessionStorage, 12 | paths 13 | }; 14 | return persist; 15 | }; 16 | 17 | export default piniaPersistConfig; 18 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import copy from "./src/copy"; 2 | import waterMarker from "./src/waterMarker"; 3 | import debounce from "./src/debounce"; 4 | import throttle from "./src/throttle"; 5 | import authButton from "./src/authButton"; 6 | 7 | const directivesList = { 8 | copy, 9 | waterMarker, 10 | debounce, 11 | throttle, 12 | authButton 13 | }; 14 | 15 | const directives = { 16 | install: function (app) { 17 | Object.keys(directivesList).forEach(key => { 18 | // 注册所有自定义指令 19 | app.directive(key, directivesList[key]); 20 | }); 21 | } 22 | }; 23 | 24 | export default directives; 25 | -------------------------------------------------------------------------------- /src/directives/src/authButton.js: -------------------------------------------------------------------------------- 1 | import { AuthStore } from "@/stores/modules/auth"; 2 | 3 | const authButton = { 4 | mounted(el, binding) { 5 | const { value } = binding; 6 | const authStore = AuthStore(); 7 | const currentPageRoles = authStore.authButtonListGet[authStore.routeName] || []; 8 | if (value instanceof Array && value.length) { 9 | const hasPermission = value.every(item => currentPageRoles.includes(item)); 10 | if (!hasPermission) el.remove(); 11 | } else { 12 | if (!currentPageRoles.includes(value)) el.remove(); 13 | } 14 | } 15 | }; 16 | 17 | export default authButton; 18 | -------------------------------------------------------------------------------- /src/directives/src/copy.js: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | const copy = { 3 | mounted(el, binding) { 4 | el.copyData = binding.value; 5 | el.addEventListener("click", handleClick); 6 | }, 7 | updated(el, binding) { 8 | el.copyData = binding.value; 9 | }, 10 | beforeUnmount(el) { 11 | el.removeEventListener("click", el.__handleClick__); 12 | } 13 | }; 14 | 15 | function handleClick() { 16 | const input = document.createElement("input"); 17 | input.value = this.copyData.toLocaleString(); 18 | document.body.appendChild(input); 19 | input.select(); 20 | document.execCommand("Copy"); 21 | document.body.removeChild(input); 22 | ElMessage({ 23 | message: "复制成功", 24 | type: "success" 25 | }); 26 | } 27 | 28 | export default copy; 29 | -------------------------------------------------------------------------------- /src/directives/src/debounce.js: -------------------------------------------------------------------------------- 1 | const debounce = { 2 | mounted(el, binding) { 3 | if (typeof binding.value !== "function") { 4 | throw "callback must be a function"; 5 | } 6 | let timer = null; 7 | el.__handleClick__ = function () { 8 | if (timer) { 9 | clearInterval(timer); 10 | } 11 | timer = setTimeout(() => { 12 | binding.value(); 13 | }, 200); 14 | }; 15 | el.addEventListener("click", el.__handleClick__); 16 | }, 17 | beforeUnmount(el) { 18 | el.removeEventListener("click", el.__handleClick__); 19 | } 20 | }; 21 | 22 | export default debounce; 23 | -------------------------------------------------------------------------------- /src/directives/src/throttle.js: -------------------------------------------------------------------------------- 1 | const throttle = { 2 | mounted(el, binding) { 3 | if (typeof binding.value !== "function") { 4 | throw "callback must be a function"; 5 | } 6 | let timer = null; 7 | el.__handleClick__ = function () { 8 | if (timer) { 9 | clearTimeout(timer); 10 | } 11 | if (!el.disabled) { 12 | el.disabled = true; 13 | binding.value(); 14 | timer = setTimeout(() => { 15 | el.disabled = false; 16 | }, 500); 17 | } 18 | }; 19 | el.addEventListener("click", el.__handleClick__); 20 | }, 21 | beforeUnmount(el) { 22 | el.removeEventListener("click", el.__handleClick__); 23 | } 24 | }; 25 | 26 | export default throttle; 27 | -------------------------------------------------------------------------------- /src/directives/src/waterMarker.js: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:给整个页面添加背景水印。 3 | 4 | 思路: 5 | 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。 6 | 2、将其设置为背景图片,从而实现页面或组件水印效果 7 | 8 | 使用:设置水印文案,颜色,字体大小即可 9 |
10 | */ 11 | 12 | const addWaterMarker = (str, parentNode, font, textColor) => { 13 | // 水印文字,父元素,字体,文字颜色 14 | let can = document.createElement("canvas"); 15 | parentNode.appendChild(can); 16 | can.width = 205; 17 | can.height = 140; 18 | can.style.display = "none"; 19 | let cans = can.getContext("2d"); 20 | cans.rotate((-20 * Math.PI) / 180); 21 | cans.font = font || "16px Microsoft JhengHei"; 22 | cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)"; 23 | cans.textAlign = "left"; 24 | cans.textBaseline = "Middle"; 25 | cans.fillText(str, can.width / 10, can.height / 2); 26 | parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")"; 27 | }; 28 | 29 | const waterMarker = { 30 | mounted(el, binding) { 31 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor); 32 | } 33 | }; 34 | 35 | export default waterMarker; 36 | -------------------------------------------------------------------------------- /src/hooks/useAuthButtons.js: -------------------------------------------------------------------------------- 1 | import { AuthStore } from "@/stores/modules/auth"; 2 | 3 | /** 4 | * @description 页面按钮权限 5 | * */ 6 | export const useAuthButtons = () => { 7 | const authStore = AuthStore(); 8 | const authButtons = authStore.authButtonListGet || []; 9 | let currentPageAuthButton = {}; 10 | authButtons.authButton.forEach(item => (currentPageAuthButton[item] = true)); 11 | return currentPageAuthButton; 12 | }; 13 | -------------------------------------------------------------------------------- /src/hooks/useEcharts.js: -------------------------------------------------------------------------------- 1 | import { onActivated, onDeactivated, onBeforeUnmount } from "vue"; 2 | // import echarts from "@/plugins/echarts"; 3 | export const useEcharts = (myChart, options) => { 4 | if (options && typeof options === "object") { 5 | myChart.setOption(options); 6 | } 7 | const echartsResize = () => { 8 | myChart && myChart.resize(); 9 | }; 10 | 11 | window.addEventListener("resize", echartsResize); 12 | 13 | onActivated(() => { 14 | window.addEventListener("resize", echartsResize); 15 | }); 16 | 17 | onDeactivated(() => { 18 | window.removeEventListener("resize", echartsResize); 19 | }); 20 | 21 | onBeforeUnmount(() => { 22 | window.removeEventListener("resize", echartsResize); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/useTheme.js: -------------------------------------------------------------------------------- 1 | import { computed, onBeforeMount } from "vue"; 2 | import { getLightColor, getDarkColor } from "@/utils/tool"; 3 | import { GlobalStore } from "@/stores"; 4 | import { DEFAULT_PRIMARY } from "@/config/config"; 5 | import { ElMessage } from "element-plus"; 6 | 7 | /** 8 | * @description 切换主题 9 | * */ 10 | export const useTheme = () => { 11 | const globalStore = GlobalStore(); 12 | const themeConfig = computed(() => globalStore.themeConfig); 13 | 14 | // 修改主题颜色 15 | const changePrimary = val => { 16 | if (!val) { 17 | val = DEFAULT_PRIMARY; 18 | ElMessage({ type: "success", message: `主题颜色已重置为 ${DEFAULT_PRIMARY}` }); 19 | } 20 | globalStore.setThemeConfig({ ...themeConfig.value, primary: val }); 21 | document.documentElement.style.setProperty("--el-color-primary", themeConfig.value.primary); 22 | document.documentElement.style.setProperty( 23 | "--el-color-primary-dark-2", 24 | themeConfig.value.isDark 25 | ? `${getLightColor(themeConfig.value.primary, 0.2)}` 26 | : `${getDarkColor(themeConfig.value.primary, 0.3)}` 27 | ); 28 | // 颜色加深或变浅 29 | for (let i = 1; i <= 9; i++) { 30 | document.documentElement.style.setProperty( 31 | `--el-color-primary-light-${i}`, 32 | themeConfig.value.isDark 33 | ? `${getDarkColor(themeConfig.value.primary, i / 10)}` 34 | : `${getLightColor(themeConfig.value.primary, i / 10)}` 35 | ); 36 | } 37 | }; 38 | // 切换黑夜模式 39 | const switchDark = () => { 40 | const body = document.documentElement; 41 | if (themeConfig.value.isDark) body.setAttribute("class", "dark"); 42 | else body.setAttribute("class", ""); 43 | changePrimary(themeConfig.value.primary); 44 | }; 45 | 46 | onBeforeMount(() => { 47 | changePrimary(themeConfig.value.primary); 48 | }); 49 | 50 | // 初始化 theme 配置 51 | const initTheme = () => { 52 | switchDark(); 53 | changePrimary(themeConfig.value.primary); 54 | }; 55 | 56 | return { 57 | changePrimary, 58 | switchDark, 59 | initTheme 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/languages/index.js: -------------------------------------------------------------------------------- 1 | import { createI18n } from "vue-i18n"; 2 | import zh from "./modules/zh"; 3 | import en from "./modules/en"; 4 | 5 | const i18n = createI18n({ 6 | legacy: false, // 如果要支持 compositionAPI,此项必须设置为 false 7 | locale: "zh", // 设置语言类型 8 | globalInjection: true, // 全局注册$t方法 9 | messages: { 10 | zh, 11 | en 12 | } 13 | }); 14 | 15 | export default i18n; 16 | -------------------------------------------------------------------------------- /src/languages/modules/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | name: "English" 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /src/languages/modules/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | test: { 3 | name: "中文" 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /src/layouts/LayoutClassic/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | .el-header { 5 | box-sizing: border-box; 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | height: 55px; 10 | padding: 0 15px 0 0; 11 | background-color: #191a20; 12 | border-bottom: 1px solid #f1f1f1; 13 | .header-lf { 14 | display: flex; 15 | align-items: center; 16 | .logo { 17 | width: 210px; 18 | margin-right: 16px; 19 | span { 20 | font-size: 14px; 21 | font-weight: bold; 22 | color: #dadada; 23 | white-space: nowrap; 24 | } 25 | img { 26 | width: 28px; 27 | object-fit: contain; 28 | margin-right: 6px; 29 | } 30 | } 31 | } 32 | :deep(.tool-bar-lf) { 33 | color: #ffffff; 34 | .el-breadcrumb__inner.is-link { 35 | color: #e5eaf3; 36 | &:hover { 37 | color: var(--el-color-primary); 38 | } 39 | } 40 | .el-breadcrumb__item:last-child .el-breadcrumb__inner, 41 | .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover { 42 | color: #cfd3dc; 43 | } 44 | } 45 | :deep(.tool-bar-ri) { 46 | .toolBar-icon, 47 | .username { 48 | color: #e5eaf3; 49 | } 50 | } 51 | } 52 | .el-aside { 53 | width: auto; 54 | overflow: inherit; 55 | background-color: #ffffff; 56 | border-right: 1px solid var(--el-border-color); 57 | transition: all 0.3s ease; 58 | .menu { 59 | display: flex; 60 | flex-direction: column; 61 | height: 100%; 62 | transition: all 0.3s ease; 63 | .el-menu { 64 | overflow-x: hidden; 65 | border-right: none; 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/layouts/LayoutClassic/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 38 | 39 | 61 | 62 | 65 | 66 | 99 | -------------------------------------------------------------------------------- /src/layouts/LayoutColumns/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | .aside-split { 5 | display: flex; 6 | flex-direction: column; 7 | flex-shrink: 0; 8 | width: 70px; 9 | height: 100%; 10 | background-color: #191a20; 11 | border-right: 1px solid #ffffff; 12 | .logo { 13 | box-sizing: border-box; 14 | height: 55px; 15 | border-bottom: 1px solid #282a35; 16 | img { 17 | width: 32px; 18 | object-fit: contain; 19 | } 20 | } 21 | .el-scrollbar { 22 | height: calc(100% - 55px); 23 | .split-list { 24 | flex: 1; 25 | .split-item { 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | justify-content: center; 30 | height: 70px; 31 | cursor: pointer; 32 | transition: all 0.3s ease; 33 | &:hover { 34 | background-color: #292b35; 35 | } 36 | .el-icon { 37 | font-size: 21px; 38 | } 39 | .title { 40 | margin-top: 6px; 41 | font-size: 12px; 42 | transform: scale(0.96); 43 | } 44 | .el-icon, 45 | .title { 46 | color: #e5eaf3; 47 | } 48 | } 49 | .split-active { 50 | background-color: $primary-color !important; 51 | .el-icon, 52 | .title { 53 | color: #ffffff; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | .el-aside { 60 | display: flex; 61 | flex-direction: column; 62 | height: 100%; 63 | overflow: hidden; 64 | background-color: #ffffff; 65 | border-right: 1px solid #f0eded; 66 | transition: all 0.3s ease; 67 | .el-scrollbar { 68 | height: calc(100% - 55px); 69 | .el-menu { 70 | overflow-x: hidden; 71 | border-right: none; 72 | } 73 | } 74 | .logo { 75 | box-sizing: border-box; 76 | height: 55px; 77 | border-bottom: 1px solid #f0eded; 78 | span { 79 | font-size: 14px; 80 | font-weight: bold; 81 | color: var(--el-menu-text-color); 82 | white-space: nowrap; 83 | } 84 | } 85 | } 86 | .not-aside { 87 | width: 0 !important; 88 | } 89 | .el-header { 90 | box-sizing: border-box; 91 | display: flex; 92 | align-items: center; 93 | justify-content: space-between; 94 | height: 55px; 95 | padding: 0 15px; 96 | background-color: #ffffff; 97 | border-bottom: 1px solid #f1f1f1; 98 | :deep(.tool-bar-ri) { 99 | .toolBar-icon, 100 | .username { 101 | color: var(--el-text-color-primary); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/layouts/LayoutColumns/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 51 | 52 | 99 | 100 | 103 | 104 | 125 | -------------------------------------------------------------------------------- /src/layouts/LayoutTransverse/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | .el-header { 5 | box-sizing: border-box; 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | height: 55px; 10 | padding: 0 15px 0 0; 11 | background-color: #191a20; 12 | border-bottom: 1px solid #f1f1f1; 13 | .logo { 14 | width: 210px; 15 | margin-right: 30px; 16 | span { 17 | font-size: 14px; 18 | font-weight: bold; 19 | color: #dadada; 20 | white-space: nowrap; 21 | } 22 | img { 23 | width: 28px; 24 | object-fit: contain; 25 | margin-right: 6px; 26 | } 27 | } 28 | :deep(.el-menu) { 29 | flex: 1; 30 | overflow: hidden; 31 | border-bottom: none; 32 | .is-active { 33 | background-color: var(--el-color-primary) !important; 34 | border-bottom-color: var(--el-color-primary) !important; 35 | .el-sub-menu__title { 36 | color: #ffffff !important; 37 | background-color: var(--el-color-primary) !important; 38 | border-bottom-color: var(--el-color-primary) !important; 39 | } 40 | &:hover { 41 | color: #ffffff !important; 42 | } 43 | } 44 | } 45 | :deep(.tool-bar-ri) { 46 | .toolBar-icon, 47 | .username { 48 | color: #e5eaf3; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/layouts/LayoutTransverse/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 44 | 45 | 65 | 66 | 69 | 70 | 107 | -------------------------------------------------------------------------------- /src/layouts/LayoutVertical/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | .el-aside { 5 | width: auto; 6 | overflow: inherit; 7 | background-color: #191a20; 8 | border-right: 1px solid #191a20; 9 | .menu { 10 | display: flex; 11 | flex-direction: column; 12 | height: 100%; 13 | transition: all 0.3s ease; 14 | .el-scrollbar { 15 | height: calc(100% - 55px); 16 | .el-menu { 17 | overflow-x: hidden; 18 | border-right: none; 19 | } 20 | } 21 | .logo { 22 | box-sizing: border-box; 23 | height: 55px; 24 | border-bottom: 1px solid #282a35; 25 | span { 26 | font-size: 14px; 27 | font-weight: bold; 28 | color: #dadada; 29 | white-space: nowrap; 30 | } 31 | img { 32 | width: 28px; 33 | object-fit: contain; 34 | margin-right: 6px; 35 | } 36 | } 37 | } 38 | } 39 | .el-header { 40 | box-sizing: border-box; 41 | display: flex; 42 | align-items: center; 43 | justify-content: space-between; 44 | height: 55px; 45 | padding: 0 15px; 46 | background-color: #ffffff; 47 | border-bottom: 1px solid #f1f1f1; 48 | :deep(.tool-bar-ri) { 49 | .toolBar-icon, 50 | .username { 51 | color: var(--el-text-color-primary); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/layouts/LayoutVertical/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 35 | 36 | 57 | 58 | 61 | 62 | 83 | -------------------------------------------------------------------------------- /src/layouts/components/Footer/index.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | height: 30px; 3 | background: #ffffff; 4 | border-top: 1px solid #e4e7ed; 5 | a { 6 | font-size: 14px; 7 | color: #858585; 8 | text-decoration: none; 9 | letter-spacing: 0.5px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarLeft.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarRight.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 45 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 44 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/CollapseIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/ColorPicker.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 35 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 5 | 33 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Language.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/LockScreen.vue: -------------------------------------------------------------------------------- 1 | 4 | 35 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/SearchMenu.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 93 | 94 | 125 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/ThemeSetting.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /src/layouts/components/Main/components/Maximize.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | 54 | -------------------------------------------------------------------------------- /src/layouts/components/Main/index.scss: -------------------------------------------------------------------------------- 1 | .el-main { 2 | box-sizing: border-box; 3 | padding: 10px 12px; 4 | overflow-x: hidden; 5 | background-color: #f0f2f5; 6 | &::-webkit-scrollbar { 7 | background-color: #f0f2f5; 8 | } 9 | } 10 | .el-footer { 11 | height: auto; 12 | padding: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/layouts/components/Main/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /src/layouts/components/Menu/SubMenu.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /src/layouts/components/Tabs/components/MoreButton.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 83 | 84 | 87 | -------------------------------------------------------------------------------- /src/layouts/components/Tabs/index.scss: -------------------------------------------------------------------------------- 1 | .tabs-box { 2 | background-color: #ffffff; 3 | :deep(.tabs-menu) { 4 | position: relative; 5 | width: 100%; 6 | .el-dropdown { 7 | position: absolute; 8 | top: 8px; 9 | right: 13px; 10 | } 11 | .tabs-icon { 12 | top: 2px; 13 | font-size: 15px; 14 | } 15 | .el-tabs__nav-wrap { 16 | position: absolute; 17 | width: calc(100% - 110px); 18 | } 19 | .el-tabs--card > .el-tabs__header { 20 | box-sizing: border-box; 21 | height: 40px; 22 | margin: 0; 23 | } 24 | .el-tabs--card > .el-tabs__header .el-tabs__nav { 25 | border: none; 26 | } 27 | .el-tabs--card > .el-tabs__header .el-tabs__item { 28 | color: #cccccc; 29 | border: none; 30 | padding: 0 4px; 31 | } 32 | .el-tabs__item .is-icon-close svg { 33 | margin-top: 0.5px; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/layouts/components/Tabs/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 113 | 114 | 117 | -------------------------------------------------------------------------------- /src/layouts/components/ThemeDrawer/index.scss: -------------------------------------------------------------------------------- 1 | .divider { 2 | margin-top: 15px; 3 | .el-icon { 4 | position: relative; 5 | top: 2px; 6 | right: 5px; 7 | font-size: 15px; 8 | } 9 | } 10 | .theme-item { 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-between; 14 | margin: 14px 0; 15 | span { 16 | font-size: 14px; 17 | } 18 | } 19 | .layout-box { 20 | position: relative; 21 | display: flex; 22 | flex-wrap: wrap; 23 | justify-content: space-around; 24 | padding: 10px 0 0; 25 | .layout-item { 26 | position: relative; 27 | box-sizing: border-box; 28 | width: 95px; 29 | height: 67px; 30 | padding: 6px; 31 | margin-bottom: 20px; 32 | cursor: pointer; 33 | border-radius: 5px; 34 | box-shadow: 0 0 5px 1px var(--el-border-color-lighter); 35 | transition: all 0.2s; 36 | .layout-dark { 37 | background-color: var(--el-color-primary); 38 | border-radius: 3px; 39 | } 40 | .layout-light { 41 | background-color: var(--el-color-primary-light-5); 42 | border-radius: 3px; 43 | } 44 | .layout-content { 45 | background-color: var(--el-color-primary-light-8); 46 | border: 1px dashed var(--el-color-primary); 47 | border-radius: 3px; 48 | } 49 | .el-icon { 50 | position: absolute; 51 | right: 10px; 52 | bottom: 10px; 53 | color: var(--el-color-primary); 54 | transition: all 0.2s; 55 | } 56 | &:hover { 57 | box-shadow: 0 0 5px 1px var(--el-border-color-darker); 58 | } 59 | } 60 | .is-active { 61 | box-shadow: 0 0 0 2px var(--el-color-primary) !important; 62 | } 63 | .layout-vertical { 64 | display: flex; 65 | justify-content: space-between; 66 | .layout-dark { 67 | width: 20%; 68 | } 69 | .layout-container { 70 | display: flex; 71 | flex-direction: column; 72 | justify-content: space-between; 73 | width: 72%; 74 | .layout-light { 75 | height: 20%; 76 | } 77 | .layout-content { 78 | height: 67%; 79 | } 80 | } 81 | } 82 | .layout-classic { 83 | display: flex; 84 | flex-direction: column; 85 | justify-content: space-between; 86 | .layout-dark { 87 | height: 22%; 88 | } 89 | .layout-container { 90 | display: flex; 91 | justify-content: space-between; 92 | height: 70%; 93 | .layout-light { 94 | width: 20%; 95 | } 96 | .layout-content { 97 | width: 70%; 98 | } 99 | } 100 | } 101 | .layout-transverse { 102 | display: flex; 103 | flex-direction: column; 104 | justify-content: space-between; 105 | .layout-dark { 106 | height: 20%; 107 | } 108 | .layout-content { 109 | height: 67%; 110 | } 111 | } 112 | .layout-columns { 113 | display: flex; 114 | justify-content: space-between; 115 | .layout-dark { 116 | width: 14%; 117 | } 118 | .layout-light { 119 | width: 17%; 120 | } 121 | .layout-content { 122 | width: 55%; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/layouts/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /src/layouts/indexAsync.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import "@/styles/reset.scss"; 4 | import "@/styles/common.scss"; 5 | import "@/assets/iconfont/iconfont.scss"; 6 | // import "@/assets/fonts/font.scss"; 7 | import elementIcon from "@/plugins/element-icon"; 8 | import "element-plus/dist/index.css"; 9 | import "@/styles/element.scss"; 10 | import "@/styles/element-dark.scss"; 11 | import "element-plus/theme-chalk/dark/css-vars.css"; 12 | // 路由 13 | import router from "@/routers/index"; 14 | // i18n 15 | import I18n from "@/languages/index"; 16 | // pinia 17 | import pinia from "@/stores/index"; 18 | // svg icons 19 | import "virtual:svg-icons-register"; 20 | // 全局错误提示 21 | import errorHandler from "@/utils/errorHandler"; 22 | // 自定义指令 23 | import directives from "@/directives/index"; 24 | // mockJs 25 | import "./mock/index"; 26 | 27 | const app = createApp(App); 28 | 29 | app.config.errorHandler = errorHandler; 30 | 31 | app.use(router).use(I18n).use(pinia).use(elementIcon).use(directives).mount("#app"); 32 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from "mockjs"; 2 | import DynamicRouter from "@/assets/json/routerList.json"; 3 | import authButton from "@/assets/json/authButton.json"; 4 | 5 | Mock.mock("/api/router/list", () => { 6 | { 7 | return Mock.mock({ 8 | code: 200, 9 | list: DynamicRouter.data 10 | }); 11 | } 12 | }); 13 | 14 | Mock.mock("/api/login", () => { 15 | { 16 | return Mock.mock({ 17 | name: "admin", 18 | token: "tokentest" 19 | }); 20 | } 21 | }); 22 | 23 | Mock.mock("/api/auth/button", () => { 24 | { 25 | return Mock.mock({ 26 | code: 200, 27 | list: authButton.data 28 | }); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/plugins/echarts.js: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts/core"; 2 | 3 | import { 4 | BarChart, 5 | LineChart, 6 | PieChart, 7 | MapChart, 8 | PictorialBarChart, 9 | RadarChart, 10 | ScatterChart, 11 | LinesChart, 12 | GraphChart 13 | } from "echarts/charts"; 14 | 15 | import { 16 | TitleComponent, 17 | TooltipComponent, 18 | GridComponent, 19 | PolarComponent, 20 | AriaComponent, 21 | ParallelComponent, 22 | LegendComponent, 23 | RadarComponent, 24 | ToolboxComponent, 25 | DataZoomComponent, 26 | VisualMapComponent, 27 | TimelineComponent, 28 | CalendarComponent, 29 | GraphicComponent 30 | } from "echarts/components"; 31 | 32 | import { LabelLayout, UniversalTransition } from "echarts/features"; 33 | 34 | import { CanvasRenderer } from "echarts/renderers"; 35 | 36 | // 注册必须的组件 37 | echarts.use([ 38 | LegendComponent, 39 | TitleComponent, 40 | TooltipComponent, 41 | GridComponent, 42 | PolarComponent, 43 | AriaComponent, 44 | ParallelComponent, 45 | BarChart, 46 | LineChart, 47 | PieChart, 48 | MapChart, 49 | RadarChart, 50 | PictorialBarChart, 51 | RadarComponent, 52 | ToolboxComponent, 53 | DataZoomComponent, 54 | VisualMapComponent, 55 | TimelineComponent, 56 | CalendarComponent, 57 | GraphicComponent, 58 | ScatterChart, 59 | LabelLayout, 60 | UniversalTransition, 61 | CanvasRenderer, 62 | LinesChart, 63 | GraphChart 64 | ]); 65 | 66 | // 导出 67 | export default echarts; 68 | -------------------------------------------------------------------------------- /src/plugins/element-icon.js: -------------------------------------------------------------------------------- 1 | import * as components from "@element-plus/icons-vue"; 2 | export default { 3 | install: app => { 4 | for (const key in components) { 5 | const componentConfig = components[key]; 6 | app.component(componentConfig.name, componentConfig); 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/routers/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | import { AuthStore } from "@/stores/modules/auth"; 3 | import { GlobalStore } from "@/stores"; 4 | import { initDynamicRouter } from "@/routers/modules/dynamicRouter"; 5 | import { staticRouter, errorRouter } from "@/routers/modules/staticRouter"; 6 | import NProgress from "@/config/nprogress"; 7 | import { LOGIN_URL, ROUTER_WHITE_LIST } from "@/config/config"; 8 | 9 | /** 10 | * @description 动态路由参数配置简介 11 | * @param path ==> 菜单路径 12 | * @param name ==> 菜单别名 13 | * @param redirect ==> 重定向地址 14 | * @param component ==> 视图文件路径 15 | * @param meta ==> 菜单信息 16 | * @param meta.icon ==> 菜单图标 17 | * @param title ==> 菜单标题 18 | * @param meta.activeMenu ==> 当前路由为详情页时,需要高亮的菜单 19 | * @param meta.isLink ==> 是否外链 20 | * @param meta.isHide ==> 是否隐藏 21 | * @param meta.isFull ==> 是否全屏(示例:数据大屏页面) 22 | * @param meta.isAffix ==> 是否固定在 tabs nav 23 | * @param meta.isKeepAlive ==> 是否缓存 24 | * */ 25 | const router = createRouter({ 26 | history: createWebHashHistory(), 27 | routes: [...staticRouter, ...errorRouter], 28 | strict: false, 29 | scrollBehavior: () => ({ left: 0, top: 0 }) 30 | }); 31 | /** 32 | * @description 路由拦截 beforeEach 33 | * */ 34 | router.beforeEach(async (to, from, next) => { 35 | const globalStore = GlobalStore(); 36 | NProgress.start(); 37 | 38 | const title = import.meta.env.VITE_GLOB_APP_TITLE; 39 | document.title = to.title ? `${to.title} - ${title}` : title; 40 | if (to.path === LOGIN_URL) { 41 | if (globalStore.token) return next(from.fullPath); 42 | resetRouter(); 43 | return next(); 44 | } 45 | if (ROUTER_WHITE_LIST.includes(to.path)) return next(); 46 | if (!globalStore.token) return next({ path: LOGIN_URL, replace: true }); 47 | 48 | const authStore = AuthStore(); 49 | authStore.setRouteName(to.name); 50 | if (!authStore.authMenuListGet.length) { 51 | await initDynamicRouter(); 52 | return next({ ...to, replace: true }); 53 | } 54 | next(); 55 | }); 56 | 57 | /** 58 | * @description 重置路由 59 | * */ 60 | export const resetRouter = () => { 61 | const authStore = AuthStore(); 62 | authStore.flatMenuListGet.forEach(route => { 63 | const { name } = route; 64 | if (name && router.hasRoute(name)) router.removeRoute(name); 65 | }); 66 | }; 67 | 68 | /** 69 | * @description 路由跳转结束 70 | * */ 71 | router.afterEach(() => { 72 | NProgress.done(); 73 | }); 74 | 75 | /** 76 | * @description 路由跳转错误 77 | * */ 78 | router.onError(error => { 79 | NProgress.done(); 80 | console.warn("路由错误", error.message); 81 | }); 82 | 83 | export default router; 84 | -------------------------------------------------------------------------------- /src/routers/modules/dynamicRouter.js: -------------------------------------------------------------------------------- 1 | import router from "@/routers/index"; 2 | import { isType } from "@/utils/util"; 3 | import { LOGIN_URL } from "@/config/config"; 4 | import { AuthStore } from "@/stores/modules/auth"; 5 | import { notFoundRouter } from "@/routers/modules/staticRouter"; 6 | // 引入 views 文件夹下所有 vue 文件 7 | const modules = import.meta.glob("@/views/**/*.vue"); 8 | 9 | /** 10 | * 初始化动态路由 11 | */ 12 | export const initDynamicRouter = async () => { 13 | let list = []; 14 | const authRouter = JSON.parse(localStorage.getItem("router")) || []; 15 | 16 | const authStore = AuthStore(); 17 | try { 18 | await authStore.getAuthMenuList(); 19 | await authStore.getAuthButtonList(); 20 | if (authRouter.length > 0) { 21 | authRouter.forEach(e1 => { 22 | authStore.flatMenuListGet.forEach(e2 => { 23 | if (e1 === e2.id) { 24 | list.push(e2); 25 | } 26 | }); 27 | }); 28 | } else { 29 | list = authStore.flatMenuListGet; 30 | } 31 | // 添加动态路由 32 | list.forEach(item => { 33 | item.children && delete item.children; 34 | if (item.component && isType(item.component) == "string") { 35 | item.component = modules["/src/views" + item.component + ".vue"]; 36 | } 37 | if (item.meta.isFull) { 38 | router.addRoute(item); 39 | } else { 40 | router.addRoute("layout", item); 41 | } 42 | }); 43 | router.addRoute(notFoundRouter); 44 | } catch (error) { 45 | // 💢 当按钮 || 菜单请求出错时,重定向到登陆页 46 | router.replace(LOGIN_URL); 47 | return Promise.reject(error); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/routers/modules/staticRouter.js: -------------------------------------------------------------------------------- 1 | // import { RouteRecordRaw } from "vue-router"; 2 | import { HOME_URL, LOGIN_URL } from "@/config/config"; 3 | 4 | /** 5 | * staticRouter(静态路由) 6 | */ 7 | export const staticRouter = [ 8 | { 9 | path: "/", 10 | redirect: HOME_URL 11 | }, 12 | { 13 | path: LOGIN_URL, 14 | name: "login", 15 | component: () => import("@/views/login/index.vue"), 16 | meta: { 17 | title: "登录" 18 | } 19 | }, 20 | { 21 | path: "/layout", 22 | name: "layout", 23 | component: () => import("@/layouts/index.vue"), 24 | redirect: HOME_URL, 25 | children: [] 26 | } 27 | ]; 28 | 29 | /** 30 | * errorRouter(错误页面路由) 31 | */ 32 | export const errorRouter = [ 33 | { 34 | path: "/403", 35 | name: "403", 36 | component: () => import("@/components/ErrorMessage/403.vue"), 37 | meta: { 38 | title: "403页面" 39 | } 40 | }, 41 | { 42 | path: "/404", 43 | name: "404", 44 | component: () => import("@/components/ErrorMessage/404.vue"), 45 | meta: { 46 | title: "404页面" 47 | } 48 | }, 49 | { 50 | path: "/500", 51 | name: "500", 52 | component: () => import("@/components/ErrorMessage/500.vue"), 53 | meta: { 54 | title: "500页面" 55 | } 56 | } 57 | ]; 58 | 59 | /** 60 | * notFoundRouter(找不到路由) 61 | */ 62 | export const notFoundRouter = { 63 | path: "/:pathMatch(.*)*", 64 | name: "notFound", 65 | redirect: { name: "404" } 66 | }; 67 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | import { defineStore, createPinia } from "pinia"; 2 | // import { GlobalState, ThemeConfigProps } from "./interface"; 3 | import { DEFAULT_PRIMARY } from "@/config/config"; 4 | import piniaPersistConfig from "@/config/piniaPersist"; 5 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 6 | 7 | // defineStore 调用后返回一个函数,调用该函数获得 Store 实体 8 | export const GlobalStore = defineStore({ 9 | // id: 必须的,在所有 Store 中唯一 10 | id: "GlobalState", 11 | // state: 返回对象的函数 12 | state: () => ({ 13 | // token 14 | token: "", 15 | // userInfo 16 | userInfo: "", 17 | // element组件大小 18 | assemblySize: "default", 19 | // language 20 | language: "", 21 | // themeConfig 22 | themeConfig: { 23 | // 布局切换 ==> 纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns 24 | layout: "vertical", 25 | // 默认 primary 主题颜色 26 | primary: DEFAULT_PRIMARY, 27 | // 黑夜模式 28 | isDark: false, 29 | // 折叠菜单 30 | isCollapse: false, 31 | // 面包屑导航 32 | breadcrumb: true, 33 | // 面包屑导航图标 34 | breadcrumbIcon: true, 35 | // 标签页 36 | tabs: true, 37 | // 标签页图标 38 | tabsIcon: true, 39 | // 页脚 40 | footer: true, 41 | // 当前页面是否全屏 42 | maximize: false 43 | } 44 | }), 45 | getters: {}, 46 | actions: { 47 | // setToken 48 | setToken(token) { 49 | this.token = token; 50 | }, 51 | // setUserInfo 52 | setUserInfo(userInfo) { 53 | this.userInfo = userInfo; 54 | }, 55 | // setAssemblySizeSize 56 | setAssemblySizeSize(assemblySize) { 57 | this.assemblySize = assemblySize; 58 | }, 59 | // updateLanguage 60 | updateLanguage(language) { 61 | this.language = language; 62 | }, 63 | // setThemeConfig 64 | setThemeConfig(themeConfig) { 65 | this.themeConfig = themeConfig; 66 | } 67 | }, 68 | persist: piniaPersistConfig("GlobalState") 69 | }); 70 | 71 | // piniaPersist(持久化) 72 | const pinia = createPinia(); 73 | pinia.use(piniaPluginPersistedstate); 74 | 75 | export default pinia; 76 | -------------------------------------------------------------------------------- /src/stores/modules/auth.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { getFlatArr } from "@/utils/util"; 3 | import { getAuthButton } from "@/api/login"; 4 | import { getShowMenuList, getAllBreadcrumbList } from "@/utils/util"; 5 | import { routerList } from "@/api/login.js"; 6 | 7 | // AuthStore 8 | export const AuthStore = defineStore({ 9 | id: "AuthState", 10 | state: () => ({ 11 | // 当前页面的 router name,用来做按钮权限筛选 12 | routeName: "", 13 | // 按钮权限列表 14 | authButtonList: {}, 15 | // 菜单权限列表 16 | authMenuList: [] 17 | }), 18 | getters: { 19 | // 按钮权限列表 20 | authButtonListGet: state => state.authButtonList, 21 | // 后端返回的菜单列表 ==> 这里没有经过任何处理 22 | authMenuListGet: state => state.authMenuList, 23 | // 后端返回的菜单列表 ==> 左侧菜单栏渲染,需要去除 isHide == true 24 | showMenuListGet: state => getShowMenuList(state.authMenuList), 25 | // 扁平化之后的一维数组路由,主要用来添加动态路由 26 | flatMenuListGet: state => getFlatArr(state.authMenuList), 27 | // 所有面包屑导航列表 28 | breadcrumbListGet: state => getAllBreadcrumbList(state.authMenuList) 29 | }, 30 | actions: { 31 | // getAuthButtonList 32 | async getAuthButtonList() { 33 | const { data } = await getAuthButton(); 34 | this.authButtonList = data.list; 35 | }, 36 | // getAuthMenuList 37 | async getAuthMenuList() { 38 | const { data } = await routerList(); 39 | this.authMenuList = data.list; 40 | }, 41 | // setRouteName 42 | async setRouteName(name) { 43 | this.routeName = name; 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/stores/modules/keepAlive.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | // KeepAliveStore 3 | export const KeepAliveStore = defineStore({ 4 | id: "keepAliveStore", 5 | state: () => ({ 6 | // 当前缓存的 routerName 7 | keepLiveName: ["test"] 8 | }), 9 | getters: {}, 10 | actions: { 11 | // addKeepLiveName 12 | async addKeepLiveName(name) { 13 | !this.keepLiveName.includes(name) && this.keepLiveName.push(name); 14 | }, 15 | // removeKeepLiveName 16 | async removeKeepLiveName(name) { 17 | this.keepLiveName = this.keepLiveName.filter(item => item !== name); 18 | }, 19 | // clearMultipleKeepAlive 20 | async clearMultipleKeepAlive(keepLiveName = []) { 21 | this.keepLiveName = keepLiveName; 22 | }, 23 | // setKeepAliveName 24 | async setKeepAliveName(keepLiveName = []) { 25 | this.keepLiveName = keepLiveName; 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/stores/modules/tabs.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { TABS_WHITE_LIST } from "@/config/config"; 3 | import piniaPersistConfig from "@/config/piniaPersist"; 4 | import router from "@/routers/index"; 5 | 6 | // TabsStore 7 | export const TabsStore = defineStore({ 8 | id: "TabsState", 9 | state: () => ({ 10 | tabsMenuList: [] 11 | }), 12 | getters: {}, 13 | actions: { 14 | // Add Tabs 15 | async addTabs(tabItem) { 16 | if (TABS_WHITE_LIST.includes(tabItem.path)) return; 17 | if (this.tabsMenuList.every(item => item.path !== tabItem.path)) { 18 | this.tabsMenuList.push(tabItem); 19 | } 20 | }, 21 | // Remove Tabs 22 | async removeTabs(tabPath, isCurrent = true) { 23 | const tabsMenuList = this.tabsMenuList; 24 | if (isCurrent) { 25 | tabsMenuList.forEach((item, index) => { 26 | if (item.path !== tabPath) return; 27 | const nextTab = tabsMenuList[index + 1] || tabsMenuList[index - 1]; 28 | if (!nextTab) return; 29 | router.push(nextTab.path); 30 | }); 31 | } 32 | this.tabsMenuList = tabsMenuList.filter(item => item.path !== tabPath); 33 | }, 34 | // Close MultipleTab 35 | async closeMultipleTab(tabsMenuValue) { 36 | this.tabsMenuList = this.tabsMenuList.filter(item => { 37 | return item.path === tabsMenuValue || !item.close; 38 | }); 39 | }, 40 | // Set Tabs 41 | async setTabs(tabsMenuList) { 42 | this.tabsMenuList = tabsMenuList; 43 | }, 44 | // Set Tabs Title 45 | async setTabsTitle(title) { 46 | const nowFullPath = location.hash.substring(1); 47 | this.tabsMenuList.forEach(item => { 48 | if (item.path == nowFullPath) item.title = title; 49 | }); 50 | } 51 | }, 52 | persist: piniaPersistConfig("TabsState") 53 | }); 54 | -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | /* flex */ 2 | .flx-center { 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | .flx-justify-between { 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | } 12 | .flx-align-center { 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | /* clearfix */ 18 | .clearfix::after { 19 | display: block; 20 | height: 0; 21 | overflow: hidden; 22 | clear: both; 23 | content: ""; 24 | } 25 | 26 | /* 文字单行省略号 */ 27 | .sle { 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | white-space: nowrap; 31 | } 32 | 33 | /* 文字多行省略号 */ 34 | .mle { 35 | display: -webkit-box; 36 | overflow: hidden; 37 | -webkit-box-orient: vertical; 38 | -webkit-line-clamp: 2; 39 | } 40 | 41 | /* 文字多了自動換行 */ 42 | .break-word { 43 | word-break: break-all; 44 | word-wrap: break-word; 45 | } 46 | 47 | /* fade-transform */ 48 | .fade-transform-leave-active, 49 | .fade-transform-enter-active { 50 | transition: all 0.2s; 51 | } 52 | .fade-transform-enter-from { 53 | opacity: 0; 54 | transition: all 0.2s; 55 | transform: translateY(60px); 56 | } 57 | .fade-transform-leave-to { 58 | opacity: 0; 59 | transition: all 0.2s; 60 | transform: translateY(-60px); 61 | } 62 | 63 | /* Breadcrumb */ 64 | .breadcrumb-enter-active, 65 | .breadcrumb-leave-active { 66 | transition: all 0.2s ease; 67 | } 68 | .breadcrumb-enter-from, 69 | .breadcrumb-leave-active { 70 | opacity: 0; 71 | transform: translateX(10px); 72 | } 73 | .breadcrumb-leave-active { 74 | position: absolute; 75 | z-index: -1; 76 | } 77 | 78 | /* scroll bar */ 79 | ::-webkit-scrollbar { 80 | width: 8px; 81 | height: 8px; 82 | } 83 | ::-webkit-scrollbar-thumb { 84 | background-color: #d3d3d3; 85 | border-radius: 20px; 86 | box-shadow: inset 0 0 0 white; 87 | } 88 | 89 | /* nprogress样式 */ 90 | #nprogress .bar { 91 | background: var(--el-color-primary) !important; 92 | } 93 | #nprogress .spinner-icon { 94 | border-top-color: var(--el-color-primary) !important; 95 | border-left-color: var(--el-color-primary) !important; 96 | } 97 | #nprogress .peg { 98 | box-shadow: 0 0 10px var(--el-color-primary), 0 0 5px var(--el-color-primary) !important; 99 | } 100 | 101 | /* 外边距、内边距全局样式 */ 102 | @for $i from 0 through 40 { 103 | .mt#{$i} { 104 | margin-top: #{$i}px !important; 105 | } 106 | .mr#{$i} { 107 | margin-right: #{$i}px !important; 108 | } 109 | .mb#{$i} { 110 | margin-bottom: #{$i}px !important; 111 | } 112 | .ml#{$i} { 113 | margin-left: #{$i}px !important; 114 | } 115 | .pt#{$i} { 116 | padding-top: #{$i}px !important; 117 | } 118 | .pr#{$i} { 119 | padding-right: #{$i}px !important; 120 | } 121 | .pb#{$i} { 122 | padding-bottom: #{$i}px !important; 123 | } 124 | .pl#{$i} { 125 | padding-left: #{$i}px !important; 126 | } 127 | } 128 | 129 | .echarts-content { 130 | width: 100%; 131 | height: 100%; 132 | display: flex; 133 | flex-direction: column; 134 | align-items: center; 135 | } 136 | -------------------------------------------------------------------------------- /src/styles/element-dark.scss: -------------------------------------------------------------------------------- 1 | /* 自定义 element 暗黑模式样式 */ 2 | html.dark { 3 | // * vue-diverse-admin 4 | --geeker-bg-color: #141414; 5 | --geeker-main-bg-color: #0d0d0d; 6 | --geeker-border-light: 1px solid #4c4c4d; 7 | 8 | // * wangEditor 9 | --w-e-toolbar-color: #595959; 10 | --w-e-toolbar-bg-color: #eeeeee; 11 | --w-e-textarea-bg-color: #141414; 12 | --w-e-textarea-color: #eeeeee; 13 | 14 | // * 以下为自定义暗黑模式内容 15 | // login 16 | .login-container { 17 | background-color: var(--el-fill-color-extra-light) !important; 18 | .login-box { 19 | background-color: var(--el-mask-color) !important; 20 | .login-form { 21 | background-color: var(--el-bg-color) !important; 22 | box-shadow: 5px 5px 15px rgb(161 161 161 / 20%) !important; 23 | .logo-text { 24 | color: var(--el-text-color-primary) !important; 25 | } 26 | } 27 | } 28 | } 29 | 30 | // scroll-bar 31 | ::-webkit-scrollbar { 32 | background-color: var(--el-scrollbar-bg-color) !important; 33 | } 34 | ::-webkit-scrollbar-thumb { 35 | background-color: var(--el-border-color-darker) !important; 36 | } 37 | 38 | // layout 39 | .el-container { 40 | .el-aside { 41 | background-color: var(--geeker-bg-color) !important; 42 | border-right: var(--geeker-border-light) !important; 43 | .logo { 44 | border-bottom: var(--geeker-border-light) !important; 45 | } 46 | } 47 | .not-aside { 48 | border-right: none !important; 49 | } 50 | .el-header { 51 | background-color: var(--geeker-bg-color) !important; 52 | border-bottom-color: var(--el-border-color-light) !important; 53 | } 54 | .el-main { 55 | background-color: var(--geeker-main-bg-color) !important; 56 | .card { 57 | background-color: var(--geeker-bg-color) !important; 58 | } 59 | } 60 | .el-tabs { 61 | background-color: var(--geeker-bg-color) !important; 62 | } 63 | .el-footer { 64 | .footer { 65 | background-color: var(--geeker-bg-color) !important; 66 | border-top-color: var(--el-border-color-light) !important; 67 | a { 68 | color: var(--el-text-color-regular) !important; 69 | } 70 | } 71 | } 72 | } 73 | 74 | // menu 75 | .el-menu, 76 | .el-sub-menu, 77 | .el-menu-item, 78 | .el-sub-menu__title { 79 | background-color: var(--geeker-bg-color) !important; 80 | &:not(.is-active) { 81 | color: #bdbdc0 !important; 82 | } 83 | &.is-active { 84 | color: #ffffff !important; 85 | background-color: #000000 !important; 86 | } 87 | } 88 | .el-menu-item:not(.is-active):hover { 89 | background-color: var(--geeker-main-bg-color) !important; 90 | } 91 | .aside-split { 92 | background-color: var(--geeker-bg-color) !important; 93 | border-right: var(--geeker-border-light) !important; 94 | .logo { 95 | border-bottom: var(--geeker-border-light) !important; 96 | } 97 | } 98 | 99 | // guide 100 | #driver-highlighted-element-stage { 101 | background-color: var(--el-color-info-light-5) !important; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | /* Reset style sheet */ 2 | 3 | /* 因为使用富文本编辑器才注释掉,如果你项目没有使用富文本编辑器,可以取消注释 */ 4 | // html, 5 | // body, 6 | // div, 7 | // span, 8 | // applet, 9 | // object, 10 | // iframe, 11 | // h1, 12 | // h2, 13 | // h3, 14 | // h4, 15 | // h5, 16 | // h6, 17 | // p, 18 | // blockquote, 19 | // pre, 20 | // a, 21 | // abbr, 22 | // acronym, 23 | // address, 24 | // big, 25 | // cite, 26 | // code, 27 | // del, 28 | // dfn, 29 | // em, 30 | // img, 31 | // ins, 32 | // kbd, 33 | // q, 34 | // s, 35 | // samp, 36 | // small, 37 | // strike, 38 | // strong, 39 | // sub, 40 | // sup, 41 | // tt, 42 | // var, 43 | // b, 44 | // u, 45 | // i, 46 | // center, 47 | // dl, 48 | // dt, 49 | // dd, 50 | // ol, 51 | // ul, 52 | // li, 53 | // fieldset, 54 | // form, 55 | // label, 56 | // legend, 57 | // table, 58 | // caption, 59 | // tbody, 60 | // tfoot, 61 | // thead, 62 | // tr, 63 | // th, 64 | // td, 65 | // article, 66 | // aside, 67 | // canvas, 68 | // details, 69 | // embed, 70 | // figure, 71 | // figcaption, 72 | // footer, 73 | // header, 74 | // hgroup, 75 | // menu, 76 | // nav, 77 | // output, 78 | // ruby, 79 | // section, 80 | // summary, 81 | // time, 82 | // mark, 83 | // audio, 84 | // video { 85 | // padding: 0; 86 | // margin: 0; 87 | // font: inherit; 88 | // font-size: 100%; 89 | // vertical-align: baseline; 90 | // border: 0; 91 | // } 92 | 93 | // /* HTML5 display-role reset for older browsers */ 94 | // article, 95 | // aside, 96 | // details, 97 | // figcaption, 98 | // figure, 99 | // footer, 100 | // header, 101 | // hgroup, 102 | // menu, 103 | // nav, 104 | // section { 105 | // display: block; 106 | // } 107 | // body { 108 | // padding: 0; 109 | // margin: 0; 110 | // } 111 | // ol, 112 | // ul { 113 | // list-style: none; 114 | // } 115 | // blockquote, 116 | // q { 117 | // quotes: none; 118 | // } 119 | // blockquote::before, 120 | // blockquote::after, 121 | // q::before, 122 | // q::after { 123 | // content: ""; 124 | // content: none; 125 | // } 126 | // table { 127 | // border-spacing: 0; 128 | // border-collapse: collapse; 129 | // } 130 | html, 131 | body, 132 | #app { 133 | width: 100%; 134 | height: 100%; 135 | padding: 0; 136 | margin: 0; 137 | } 138 | 139 | /* 解决 h1 标签在 webkit 内核浏览器中文字大小失效问题 */ 140 | :-webkit-any(article, aside, nav, section) h1 { 141 | font-size: 2em; 142 | } 143 | -------------------------------------------------------------------------------- /src/styles/var.scss: -------------------------------------------------------------------------------- 1 | /* 全局 css 变量 */ 2 | $primary-color: var(--el-color-primary); 3 | -------------------------------------------------------------------------------- /src/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | 3 | /** 4 | * @description 全局代码错误捕捉 5 | * */ 6 | const errorHandler = error => { 7 | // 过滤 HTTP 请求错误 8 | if (error.status || error.status == 0) return false; 9 | let errorMap = { 10 | InternalError: "Javascript引擎内部错误", 11 | ReferenceError: "未找到对象", 12 | TypeError: "使用了错误的类型或对象", 13 | RangeError: "使用内置对象时,参数超范围", 14 | SyntaxError: "语法错误", 15 | EvalError: "错误的使用了Eval", 16 | URIError: "URI错误" 17 | }; 18 | let errorName = errorMap[error.name] || "未知错误"; 19 | ElNotification({ 20 | title: errorName, 21 | message: error, 22 | type: "error", 23 | duration: 3000 24 | }); 25 | }; 26 | 27 | export default errorHandler; 28 | -------------------------------------------------------------------------------- /src/utils/getEnv.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import dotenv from "dotenv"; 4 | 5 | declare type Recordable = Record; 6 | interface ViteEnv { 7 | VITE_API_URL: string; 8 | VITE_PORT: number; 9 | VITE_OPEN: boolean; 10 | VITE_GLOB_APP_TITLE: string; 11 | VITE_DROP_CONSOLE: boolean; 12 | VITE_PROXY_URL: string; 13 | VITE_BUILD_GZIP: boolean; 14 | VITE_REPORT: boolean; 15 | } 16 | 17 | export function isDevFn(mode: string): boolean { 18 | return mode === "development"; 19 | } 20 | 21 | export function isProdFn(mode: string): boolean { 22 | return mode === "production"; 23 | } 24 | 25 | /** 26 | * Whether to generate package preview 27 | */ 28 | export function isReportMode(): boolean { 29 | return process.env.VITE_REPORT === "true"; 30 | } 31 | 32 | // Read all environment variable configuration files to process.env 33 | export function wrapperEnv(envConf: Recordable): ViteEnv { 34 | const ret: any = {}; 35 | 36 | for (const envName of Object.keys(envConf)) { 37 | let realName = envConf[envName].replace(/\\n/g, "\n"); 38 | realName = realName === "true" ? true : realName === "false" ? false : realName; 39 | 40 | if (envName === "VITE_PORT") { 41 | realName = Number(realName); 42 | } 43 | if (envName === "VITE_PROXY") { 44 | try { 45 | realName = JSON.parse(realName); 46 | } catch (error) {} 47 | } 48 | ret[envName] = realName; 49 | process.env[envName] = realName; 50 | } 51 | return ret; 52 | } 53 | 54 | /** 55 | * Get the environment variables starting with the specified prefix 56 | * @param match prefix 57 | * @param confFiles ext 58 | */ 59 | export function getEnvConfig(match = "VITE_GLOB_", confFiles = [".env", ".env.production"]) { 60 | let envConfig = {}; 61 | confFiles.forEach(item => { 62 | try { 63 | const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))); 64 | envConfig = { ...envConfig, ...env }; 65 | } catch (error) { 66 | console.error(`Error in parsing ${item}`, error); 67 | } 68 | }); 69 | 70 | Object.keys(envConfig).forEach(key => { 71 | const reg = new RegExp(`^(${match})`); 72 | if (!reg.test(key)) { 73 | Reflect.deleteProperty(envConfig, key); 74 | } 75 | }); 76 | return envConfig; 77 | } 78 | 79 | /** 80 | * Get user root directory 81 | * @param dir file path 82 | */ 83 | export function getRootPath(...dir: string[]) { 84 | return path.resolve(process.cwd(), ...dir); 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/is.js: -------------------------------------------------------------------------------- 1 | export function is(val, type) { 2 | return toString.call(val) === `[object ${type}]`; 3 | } 4 | 5 | export function isNumber(val) { 6 | return is(val, "Number"); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/mittBus.js: -------------------------------------------------------------------------------- 1 | import mitt from "mitt"; 2 | 3 | const mittBus = mitt(); 4 | 5 | export default mittBus; 6 | -------------------------------------------------------------------------------- /src/utils/service.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | // let devUrl = import.meta.env.VITE_BASE_URL; 3 | // let prodUrl = import.meta.env.VITE_GLOB_BASE_URL; 4 | const service = axios.create({ 5 | baseURL: "", 6 | timeout: 5000 7 | }); 8 | service.interceptors.request.use( 9 | config => { 10 | return config; 11 | }, 12 | error => { 13 | return Promise.reject(error); 14 | } 15 | ); 16 | 17 | service.interceptors.response.use( 18 | res => { 19 | return res; 20 | }, 21 | error => { 22 | return Promise.reject(error); 23 | } 24 | ); 25 | 26 | export default service; 27 | -------------------------------------------------------------------------------- /src/utils/tool.js: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | 3 | /** 4 | * hex颜色转rgb颜色 5 | * @param str 颜色值字符串 6 | * @returns 返回处理后的颜色值 7 | */ 8 | export function hexToRgb(str) { 9 | let hexs = ""; 10 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 11 | if (!reg.test(str)) return ElMessage.warning("输入错误的hex"); 12 | str = str.replace("#", ""); 13 | hexs = str.match(/../g); 14 | for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16); 15 | return hexs; 16 | } 17 | 18 | /** 19 | * rgb颜色转Hex颜色 20 | * @param r 代表红色 21 | * @param g 代表绿色 22 | * @param b 代表蓝色 23 | * @returns 返回处理后的颜色值 24 | */ 25 | export function rgbToHex(r, g, b) { 26 | let reg = /^\d{1,3}$/; 27 | if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning("输入错误的rgb颜色值"); 28 | let hexs = [r.toString(16), g.toString(16), b.toString(16)]; 29 | for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`; 30 | return `#${hexs.join("")}`; 31 | } 32 | 33 | /** 34 | * 加深颜色值 35 | * @param color 颜色值字符串 36 | * @param level 加深的程度,限0-1之间 37 | * @returns 返回处理后的颜色值 38 | */ 39 | export function getDarkColor(color, level) { 40 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 41 | if (!reg.test(color)) return ElMessage.warning("输入错误的hex颜色值"); 42 | let rgb = hexToRgb(color); 43 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level)); 44 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 45 | } 46 | 47 | /** 48 | * 变浅颜色值 49 | * @param color 颜色值字符串 50 | * @param level 加深的程度,限0-1之间 51 | * @returns 返回处理后的颜色值 52 | */ 53 | export function getLightColor(color, level) { 54 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 55 | if (!reg.test(color)) return ElMessage.warning("输入错误的hex颜色值"); 56 | let rgb = hexToRgb(color); 57 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(255 * level + rgb[i] * (1 - level)); 58 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 59 | } 60 | -------------------------------------------------------------------------------- /src/views/assembly/count.vue: -------------------------------------------------------------------------------- 1 | 19 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /src/views/assembly/drag.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 83 | -------------------------------------------------------------------------------- /src/views/assembly/guide.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 58 | -------------------------------------------------------------------------------- /src/views/assembly/loading.vue: -------------------------------------------------------------------------------- 1 | 12 | 52 | 60 | -------------------------------------------------------------------------------- /src/views/assembly/message.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 72 | -------------------------------------------------------------------------------- /src/views/assembly/wangEditor.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 48 | -------------------------------------------------------------------------------- /src/views/assembly/webpage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/views/auth/authButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 19 | -------------------------------------------------------------------------------- /src/views/auth/authMenu.vue: -------------------------------------------------------------------------------- 1 | 9 | 48 | -------------------------------------------------------------------------------- /src/views/dataView/components/MapChian.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 122 | -------------------------------------------------------------------------------- /src/views/dataView/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 47 | 48 | 111 | -------------------------------------------------------------------------------- /src/views/directives/copy.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /src/views/directives/debounce.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/views/directives/throttle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/views/directives/watermark.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /src/views/home/components/HomeCount.vue: -------------------------------------------------------------------------------- 1 | 25 | 42 | 43 | 81 | -------------------------------------------------------------------------------- /src/views/home/components/HomeLine.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 145 | -------------------------------------------------------------------------------- /src/views/home/components/HomeTable.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 36 | -------------------------------------------------------------------------------- /src/views/home/components/HomeUser.vue: -------------------------------------------------------------------------------- 1 | 25 | 31 | 61 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 66 | -------------------------------------------------------------------------------- /src/views/language/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/login/components/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/views/menu/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu2-1/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu2-2/menu2-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu2-2/menu2-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/panel/components/PancePie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 57 | -------------------------------------------------------------------------------- /src/views/panel/components/PancelLine.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 165 | -------------------------------------------------------------------------------- /src/views/panel/components/progressBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 75 | -------------------------------------------------------------------------------- /src/views/work/excel.vue: -------------------------------------------------------------------------------- 1 | 26 | 90 | -------------------------------------------------------------------------------- /src/views/work/print.vue: -------------------------------------------------------------------------------- 1 | 11 | 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | // element 类型提示 9 | // "types": ["element-plus/global"], 10 | 11 | "strict": true /* Enable all strict type-checking options. */, 12 | 13 | "jsx": "preserve", 14 | "sourceMap": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "esModuleInterop": true, 18 | // 跳过第三方库检查,解决打包失败 19 | "skipLibCheck": true, 20 | "lib": ["esnext", "dom"], 21 | // 解析非相对模块名的基准目录 22 | "baseUrl": "./", 23 | // 模块名到基于 baseUrl 的路径映射的列表。 24 | "paths": { 25 | "@": ["src"], 26 | "@/*": ["src/*"] 27 | } 28 | }, 29 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "vite.config.ts", "src/routers/modules/staticRouter.js", "src/routers/modules/dynamicRouter.js", "src/main.js", "src/config/serviceLoading.js", "src/config/piniaPersist.s", "src/config/nprogress.js", "src/enums/httpEnum.js", "src/hooks/useAuthButtons.js", "src/hooks/useDownload.js", "src/hooks/useEcharts.js", "src/hooks/useHandleData.js", "src/hooks/useOnline.js", "src/hooks/useSelection.js", "src/hooks/useTable.js", "src/hooks/useTheme.js", "src/hooks/useTime.js", "src/utils/svg.js", "src/utils/serviceDict.js", "src/utils/mittBus.js", "src/utils/theme/tool.js", "src/utils/is/index.js", "src/utils/getEnv.ts", "src/utils/errorHandler.js", "src/utils/eleValidate.js", "src/stores/modules/auth.js", "src/stores/modules/keepAlive.js", "src/stores/modules/tabs.js", "src/stores/index.js", "src/utils/util.js"], 30 | "exclude": ["node_modules", "dist", "**/*.js"] 31 | } 32 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv, ConfigEnv, UserConfig } from "vite"; 2 | import { createHtmlPlugin } from "vite-plugin-html"; 3 | import vue from "@vitejs/plugin-vue"; 4 | import { resolve } from "path"; 5 | import { wrapperEnv } from "./src/utils/getEnv"; 6 | import { visualizer } from "rollup-plugin-visualizer"; 7 | import { createSvgIconsPlugin } from "vite-plugin-svg-icons"; 8 | import viteCompression from "vite-plugin-compression"; 9 | import VueSetupExtend from "vite-plugin-vue-setup-extend"; 10 | import eslintPlugin from "vite-plugin-eslint"; 11 | import vueJsx from "@vitejs/plugin-vue-jsx"; 12 | import AutoImport from "unplugin-auto-import/vite"; 13 | import Components from "unplugin-vue-components/vite"; 14 | import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; 15 | 16 | // @see: https://vitejs.dev/config/ 17 | export default defineConfig(({ mode }: ConfigEnv): UserConfig => { 18 | const env = loadEnv(mode, process.cwd()); 19 | const viteEnv = wrapperEnv(env); 20 | 21 | return { 22 | base: "./", 23 | resolve: { 24 | alias: { 25 | "@": resolve(__dirname, "./src") 26 | } 27 | }, 28 | css: { 29 | preprocessorOptions: { 30 | scss: { 31 | additionalData: `@import "@/styles/var.scss";` 32 | } 33 | } 34 | }, 35 | server: { 36 | // 服务器主机名,如果允许外部访问,可设置为 "0.0.0.0" 37 | host: "0.0.0.0", 38 | port: viteEnv.VITE_PORT, 39 | open: viteEnv.VITE_OPEN, 40 | cors: true, 41 | // 跨域代理配置 42 | proxy: { 43 | "/api": { 44 | target: "https://gitee.com", // easymock 45 | changeOrigin: true, 46 | rewrite: path => path.replace(/^\/api/, "") 47 | } 48 | } 49 | }, 50 | plugins: [ 51 | vue(), 52 | createHtmlPlugin({ 53 | inject: { 54 | data: { 55 | title: viteEnv.VITE_GLOB_APP_TITLE 56 | } 57 | } 58 | }), 59 | // * 使用 svg 图标 60 | createSvgIconsPlugin({ 61 | iconDirs: [resolve(process.cwd(), "src/assets/icons")], 62 | symbolId: "icon-[dir]-[name]" 63 | }), 64 | // * EsLint 报错信息显示在浏览器界面上 65 | eslintPlugin(), 66 | // * vite 可以使用 jsx/tsx 语法 67 | vueJsx(), 68 | // * name 可以写在 script 标签上 69 | VueSetupExtend(), 70 | // * 是否生成包预览(分析依赖包大小,方便做优化处理) 71 | viteEnv.VITE_REPORT && visualizer(), 72 | // * gzip compress 73 | viteEnv.VITE_BUILD_GZIP && 74 | viteCompression({ 75 | verbose: true, 76 | disable: false, 77 | threshold: 10240, 78 | algorithm: "gzip", 79 | ext: ".gz" 80 | }), 81 | // * demand import element 82 | AutoImport({ 83 | resolvers: [ElementPlusResolver()] 84 | }), 85 | Components({ 86 | resolvers: [ElementPlusResolver()] 87 | }) 88 | ], 89 | esbuild: { 90 | pure: viteEnv.VITE_DROP_CONSOLE ? ["console.log", "debugger"] : [] 91 | }, 92 | build: { 93 | outDir: "dist", 94 | minify: "esbuild", 95 | chunkSizeWarningLimit: 1500, 96 | rollupOptions: { 97 | output: { 98 | // Static resource classification and packaging 99 | chunkFileNames: "assets/js/[name]-[hash].js", 100 | entryFileNames: "assets/js/[name]-[hash].js", 101 | assetFileNames: "assets/[ext]/[name]-[hash].[ext]" 102 | } 103 | } 104 | } 105 | }; 106 | }); 107 | --------------------------------------------------------------------------------