├── .browserslistrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── index.js │ ├── mock-server.js │ └── modules │ │ └── system.js ├── assets │ └── logo.png ├── components │ ├── Common.vue │ ├── FunctionPage.vue │ ├── dashboard │ │ ├── LiveChart.vue │ │ └── Shortcuts.vue │ ├── layout │ │ ├── NavigateBar.vue │ │ ├── SideBar.vue │ │ └── components │ │ │ ├── Breadcrumb.vue │ │ │ ├── Hamburger.vue │ │ │ ├── Logo.vue │ │ │ ├── Personal.vue │ │ │ └── SlideMenu.vue │ └── veBaseComponents │ │ ├── VeTable.vue │ │ └── index.js ├── config.js ├── directives │ ├── index.js │ └── modules │ │ ├── permission.js │ │ └── resize.js ├── main.js ├── plugins │ ├── axios.js │ ├── element.js │ ├── mock.js │ ├── permission.js │ └── svgicon.js ├── router │ ├── globalRoutes.js │ ├── index.js │ └── mainRoutes.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ └── app │ │ ├── index.js │ │ └── type.js ├── styles │ ├── common.scss │ └── variables.scss.js ├── utils │ ├── index.js │ └── validate.js └── views │ ├── 404.vue │ ├── AppMain.vue │ ├── Home.vue │ ├── IFrame.vue │ ├── Login.vue │ └── layoutpages │ ├── common.js │ ├── leisure │ └── Game.vue │ └── system │ ├── Menus.vue │ ├── Roles.vue │ ├── UserTable.vue │ ├── Users.vue │ └── components │ ├── MenuEdit.vue │ ├── RoleEdit.vue │ ├── UsersEdit.vue │ └── UsersEditRoute.vue └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # * -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 09:49:29 4 | * @LastEditTime: 2022-04-28 18:32:51 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\.eslintrc.js 8 | */ 9 | module.exports = { 10 | root: true, 11 | 12 | env: { 13 | node: true, 14 | "vue/setup-compiler-macros": true, 15 | }, 16 | 17 | extends: [ 18 | "plugin:vue/vue3-essential", 19 | "eslint:recommended", 20 | "@vue/prettier", 21 | ], 22 | 23 | parserOptions: { 24 | parser: "@babel/eslint-parser", 25 | }, 26 | // "writable" 以允许重写变量,或 "readonly" 不允许重写变量 27 | globals: { 28 | XE: "readonly", 29 | VE_ENV: "readonly", 30 | VE_API: "readonly", 31 | }, 32 | 33 | rules: { 34 | indent: [2, 4, { SwitchCase: 1 }], 35 | "prettier/prettier": [2, { tabWidth: 4, endOfLine: "auto" }], 36 | "no-console": "off", 37 | "no-debugger": "off", 38 | "vue/multi-word-component-names": 0, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | cloudbaserc.json 18 | .idea 19 | .vscode/launch.* 20 | .history 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry="https://registry.npmjs.org/" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.tabWidth": 4, 4 | "editor.insertSpaces": false, 5 | "vetur.format.options.tabSize": 4, 6 | "editor.formatOnPaste": true, 7 | "files.autoSaveDelay": 2000, 8 | "prettier.htmlWhitespaceSensitivity": "ignore", 9 | "volar.takeOverMode.enabled": false 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Asa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ![logo](https://i.bmp.ovh/imgs/2021/08/f828888bb4064c64.png) 11 | 12 | # vue3-element-admin 13 | 14 | **[✈ 国内加速链接](https://gitee.com/asaasa/vue3-element-admin)** 15 | **[✈ 效果预览](https://vue3-element-admin.vercel.app/)** 16 | **[✈ 效果预览(备用地址)](http://asaasa.gitee.io/xujianhua)** 17 | **走过路过的老铁,帮忙点个小 ⭐⭐⭐⭐⭐,🤝🤝🤝🤝🤝,🙏🙏🙏🙏🙏** 18 | 19 | ## 项目简介 20 | 21 | 基于**vue3**和**element-plus**开发的企业后台管理模板. 22 | 23 | --- 24 | 25 | ![登录页](https://i.bmp.ovh/imgs/2021/08/d9cc587a8b230dec.png) 26 | ![404](https://i.bmp.ovh/imgs/2021/08/c8d46b772369167d.png) 27 | ![用户管理](https://i.bmp.ovh/imgs/2021/08/6ea6b416eebca641.png) 28 | ![菜单管理](https://i.bmp.ovh/imgs/2021/08/682c150eef16bf17.png) 29 | ![角色管理](https://i.bmp.ovh/imgs/2021/08/7ff5cda434a2000b.png) 30 | ![iframe页面](https://i.bmp.ovh/imgs/2021/08/a101fec1b5769d7b.png) 31 | 32 | ## 功能特性 33 | 34 | 项目使用了最新的**vue3 全家桶**+**element-plus**+**mockjs**+**axios**+**eChart5**.项目继成了**mockServe**,可脱离后端自主开发测试 35 | 对**axios**深度封装,采用动态路由,路由配置更简单,**mockServe**独立开发测试时可在 nodework 直观查看接口数据 36 | 基于 node 实现自动化开发 37 | 38 | ## 环境依赖 39 | 40 | **node 14+**, **vueCli 4+** 41 | 42 | ## 部署步骤 43 | 44 | **npm i** 45 | **npm run serve** 46 | 47 | ## 目录结构描述 48 | 49 | ``` 50 | │ .browserslistrc 浏览器兼容配置 51 | │ .eslintrc.js eslint配置文件 52 | │ .gitignore git配置文件 53 | │ babel.config.js babel配置文件 54 | │ jsconfig.json js配置文件 55 | │ LICENSE 开源认证 56 | │ package-lock.json 57 | │ package.json 58 | │ README.md 项目说明 59 | │ vue.config.js vue配置文件 60 | │ 61 | ├─.vscode vscode配置文件 62 | │ settings.json 63 | │ 64 | ├─node_modules 65 | ├─public 66 | │ favicon.ico 67 | │ index.html 68 | │ 69 | └─src 70 | │ App.vue 71 | │ main.js 72 | │ config.js 73 | │ 74 | ├─api api管理模块 75 | │ │ index.js api管理入口文件 76 | │ │ mock-server.js mock服务配置文件 77 | │ │ 78 | │ └─modules api分模块管理 79 | │ system.js 模块api文件 80 | │ 81 | ├─assets 静态文件 82 | │ logo.png 83 | │ 84 | ├─components 公共组件目录 85 | │ │ Common.vue 86 | │ │ FunctionPage.vue 87 | │ │ 88 | │ ├─dashboard 89 | │ │ LiveChart.vue 90 | │ │ Shortcuts.vue 91 | │ │ 92 | │ └─layout 93 | │ │ NavigateBar.vue 94 | │ │ SideBar.vue 95 | │ │ 96 | │ └─components 97 | │ Breadcrumb.vue 98 | │ Hamburger.vue 99 | │ Logo.vue 100 | │ Personal.vue 101 | │ SlideMenu.vue 102 | │ 103 | ├─directives 自定义指令目录 104 | │ │ index.js 自定义指令入口文件 105 | │ │ 106 | │ └─modules 自定义指令模块目录 107 | │ permission.js 108 | │ resize.js 109 | │ 110 | ├─plugins 插件目录 111 | │ axios.js 112 | │ element.js 113 | │ mock.js 114 | │ permission.js 115 | │ 116 | ├─router router目录 117 | │ globalRoutes.js 118 | │ index.js 119 | │ mainRoutes.js 120 | │ 121 | ├─store vuex目录 122 | │ │ getters.js 123 | │ │ index.js 124 | │ │ 125 | │ └─modules vuex模块目录 126 | │ app.js 127 | │ 128 | ├─styles 样式目录 129 | │ common.scss 130 | │ variables.scss.js 131 | │ 132 | ├─utils 公共方法 133 | │ index.js 134 | │ validate.js 135 | │ 136 | └─views 137 | │ 404.vue 138 | │ AppMain.vue 139 | │ Home.vue 140 | │ IFrame.vue 141 | │ Login.vue 142 | │ 143 | └─layoutpages 144 | │ common.js 145 | │ 146 | ├─leisure 147 | │ Game.vue 148 | │ 149 | └─system 150 | │ Menus.vue 151 | │ Roles.vue 152 | │ Users.vue 153 | │ 154 | └─components 155 | MenuEdit.vue 156 | RoleEdit.vue 157 | UsersEdit.vue 158 | UsersEditRoute.vue 159 | ``` 160 | 161 | ## 使用文档 162 | 163 | ### 自定义指令 164 | 165 | **v-permission="[array]"** 166 | 自定义权限指令,参数为一个数组,数组元素为按钮所对应的 key 值 167 | 168 | ```js 169 | {{ menus.add.name }} 175 | 176 | ``` 177 | 178 | **v-resize="myChart"** 179 | 监听 echart 容器的自定义指令,参数为 echart 实例 180 | 181 | ```js 182 |
187 | ``` 188 | 189 | ### 动态路由 190 | 191 | 动态路由的配置可查看 [src\plugins\permission.js](src\plugins\permission.js) 192 | 主要原理就是根据后端接口返回的树形菜单数据,动态生成路由表并挂载.具体需求字段可在[src\plugins\permission.js](src\plugins\permission.js)中的**fnAddDynamicMenuRoutes**方法中进行配置修改 193 | 194 | ```js 195 | let route = { 196 | path: menuList[i].url.replace(/\//g, "-") + `-${menuList[i].id}`, 197 | component: null, 198 | name: menuList[i].url.replace(/\//g, "-") + `-${menuList[i].id}`, 199 | // meta: { 200 | // } 201 | }; 202 | // url以http[s]://开头, 通过iframe展示 203 | if (menuList[i].iframe == 1) { 204 | route["path"] = `i-${menuList[i].id}`; 205 | route["name"] = `i-${menuList[i].id}`; 206 | route["props"] = { url: menuList[i].url }; 207 | route["component"] = () => import("@/views/IFrame.vue"); 208 | } else { 209 | const l = "views/layoutpages/" + menuList[i].url; 210 | route["component"] = () => import("@/" + l + ".vue"); 211 | } 212 | routes.push(route); 213 | ``` 214 | 215 | 根据需求可以添加更多路由配置或定制化字段,如路由别名等 216 | 217 | ### 接口配置 218 | 219 | 项目中对 axios 做了封装配置中添加**Global**字段用来控制是否显示全屏 load,默认为 true,如需修改添加 axios 配置可在[src\plugins\axios.js](src\plugins\axios.js)中进行 220 | 221 | #### 添加接口 222 | 223 | 本项目对 mock 做了深度集成,在使用其他项目时,mock 接口和项目接口往往都是分开维护很不方便.所以就放在了一起.只用在一处添加即可.接口目录为[src\api\modules](src\api\modules)**不要修改此目录名称**.在该目录下添加对应的接口文件. 224 | 225 | ```js 226 | module.exports = { 227 | login: { 228 | //接口名称 必须 229 | url: "/login", //接口地址 必须 230 | type: "post", //请求类型 必须 231 | mock: true, //mock细粒度控制开关,非必须,不填则为false 232 | response: (opt) => { 233 | //启用mock时的返回数据 opt为请求数据头 234 | const { 235 | body: { userName, pwd }, 236 | } = opt; 237 | let data = { 238 | code: "00", 239 | message: "登录成功!", 240 | token: new Date().getTime(), 241 | uname: userName, 242 | }; 243 | if (userName == "Administrator") { 244 | if (pwd != "123456") { 245 | data = { 246 | code: "01", 247 | message: "密码错误", 248 | }; 249 | } 250 | } 251 | return data; 252 | }, 253 | }, 254 | }; 255 | ``` 256 | 257 | 必须使用**module.exports**导出 258 | 259 | #### 接口的使用 260 | 261 | 项目中已将封装后 axios 实例挂载到自定义字段 window.VE_API 上.调用方式为: 262 | 263 | ```js 264 | VE_API [ fileName ][ portName ] (params,[config]) //有全局loading 265 | VE_API [ fileName ][ portName ] (params,{Global:false) //没有全局loading 266 | ``` 267 | 268 | ### 菜单配置 269 | 270 | 项目中的菜单时根据后端返回的树形结构数据动态生成,具体可查看[src\components\layout\components\SlideMenu.vue](src\components\layout\components\SlideMenu.vue) 271 | 272 | ## 声明 273 | 274 | 个人开发维护! 欢迎交流学习! 275 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 09:49:29 4 | * @LastEditTime: 2021-10-14 16:15:30 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\babel.config.js 8 | */ 9 | module.exports = { 10 | presets: ["@vue/cli-plugin-babel/preset"], 11 | plugins: ["@vue/babel-plugin-jsx"], 12 | }; 13 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "include": ["src/*"] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-element-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "^1.1.4", 12 | "@vueuse/core": "^8.3.1", 13 | "axios": "^0.26.1", 14 | "core-js": "^3.22.3", 15 | "dayjs": "^1.11.1", 16 | "echarts": "^5.3.2", 17 | "element-plus": "^2.1.11", 18 | "normalize.css": "^8.0.1", 19 | "nprogress": "^0.2.0", 20 | "qs": "^6.10.3", 21 | "vue": "^3.2.33", 22 | "vue-router": "^4.0.14", 23 | "vuex": "^4.0.2", 24 | "xe-utils": "^3.5.4", 25 | "zdog": "^1.1.3" 26 | }, 27 | "devDependencies": { 28 | "@babel/eslint-parser": "^7.17.0", 29 | "@vue/babel-plugin-jsx": "^1.1.1", 30 | "@vue/cli-plugin-babel": "^5.0.4", 31 | "@vue/cli-plugin-eslint": "^5.0.4", 32 | "@vue/cli-plugin-router": "^5.0.4", 33 | "@vue/cli-plugin-vuex": "^5.0.4", 34 | "@vue/cli-service": "^5.0.4", 35 | "@vue/compiler-sfc": "^3.2.33", 36 | "@vue/eslint-config-prettier": "^7.0.0", 37 | "compression-webpack-plugin": "^9.2.0", 38 | "eslint": "^8.14.0", 39 | "eslint-plugin-prettier": "^4.0.0", 40 | "eslint-plugin-vue": "^8.7.1", 41 | "lint-staged": "^12.4.1", 42 | "mockjs": "^1.1.0", 43 | "prettier": "^2.6.2", 44 | "sass": "^1.51.0", 45 | "sass-loader": "^12.6.0", 46 | "terser-webpack-plugin": "^5.3.1", 47 | "vue-cli-plugin-axios": "^0.0.4", 48 | "vue-cli-plugin-element-plus": "^0.0.13", 49 | "webpack": "^5.72.0" 50 | }, 51 | "gitHooks": { 52 | "pre-commit": "lint-staged" 53 | }, 54 | "lint-staged": { 55 | "*.{js,jsx,vue}": [ 56 | "vue-cli-service lint --fix" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq929323125/vue3-element-admin/dc1015714cf4bbd82bd3f590e588a528d932a13b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <%= htmlWebpackPlugin.options.title %> 17 | 18 | 19 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-05 10:19:11 4 | * @LastEditTime: 2021-02-07 12:01:46 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue_3.0_test\src\mock\index.js 8 | */ 9 | const path = require("path"); 10 | const fs = require("fs"); 11 | const getPathInfo = (p) => path.parse(p); 12 | 13 | /** 14 | * @description // 递归读取文件,类似于webpack的require.context() 15 | * 16 | * @param {String} directory 文件目录 17 | * @param {Boolean} useSubdirectories 是否查询子目录,默认false 18 | * @param {array} extList 查询文件后缀,默认 ['.js'] 19 | * 20 | */ 21 | function autoLoadFile(directory, useSubdirectories = false, extList = [".js"]) { 22 | const filesList = {}; 23 | // 递归读取文件 24 | function readFileList(directory, useSubdirectories, extList) { 25 | const files = fs.readdirSync(directory); 26 | files.forEach((item) => { 27 | const fullPath = path.join(directory, item); 28 | const stat = fs.statSync(fullPath); 29 | if (stat.isDirectory() && useSubdirectories) { 30 | readFileList( 31 | path.join(directory, item), 32 | useSubdirectories, 33 | extList 34 | ); 35 | } else { 36 | const info = getPathInfo(fullPath); 37 | 38 | if (extList.includes(info.ext)) { 39 | filesList[info.name] = require(fullPath); 40 | } 41 | } 42 | }); 43 | } 44 | readFileList(directory, useSubdirectories, extList); 45 | 46 | return filesList; 47 | } 48 | 49 | module.exports = autoLoadFile(path.join(__dirname, "./modules")); 50 | -------------------------------------------------------------------------------- /src/api/mock-server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-05 17:15:27 4 | * @LastEditTime: 2021-04-25 10:44:11 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue_3.0_test\src\mock\mock-server.js 8 | */ 9 | 10 | // const bodyParser = require("body-parser"); 11 | const express = require("express"); 12 | const chokidar = require("chokidar"); 13 | const chalk = require("chalk"); 14 | const path = require("path"); 15 | const Mock = require("mockjs"); 16 | const apiDir = path.join(process.cwd(), "src/api"); 17 | 18 | // 注册mock接口路径 19 | function registerRoutes(app) { 20 | let mockLastIndex; 21 | let mocksForServer = new Array(); 22 | const api = require("./index.js"); 23 | Object.keys(api).map((route) => { 24 | Object.keys(api[route]).map((item) => { 25 | api[route][item].mock && 26 | mocksForServer.push( 27 | responseFake( 28 | api[route][item].url, 29 | api[route][item].type, 30 | api[route][item].response 31 | ) 32 | ); 33 | }); 34 | }); 35 | // 注册接口 36 | for (const mock of mocksForServer) { 37 | app[mock.type](mock.url, mock.response); 38 | mockLastIndex = app._router.stack.length; 39 | // console.log(app._router.stack[12]) 40 | } 41 | // 获取接口的长度 42 | const mockRoutesLength = mocksForServer.length; 43 | // 注意:mockRoutesLength并不等于定于路由路径的数量,还包括其他路由 44 | // console.log(mockRoutesLength,mockLastIndex) 45 | return { 46 | mockRoutesLength, 47 | mockStartIndex: mockLastIndex - mockRoutesLength, 48 | }; 49 | } 50 | // 模拟mock server 51 | const responseFake = (url, type, respond) => { 52 | return { 53 | url: url, 54 | type: type || "get", 55 | response: (req, res) => { 56 | console.log(chalk.red("请求", req.path)); 57 | res.json( 58 | Mock.mock( 59 | respond instanceof Function ? respond(req, res) : respond 60 | ) 61 | ); 62 | }, 63 | }; 64 | }; 65 | // 移除路由 66 | function unregisterRoutes() { 67 | Object.keys(require.cache).forEach((i) => { 68 | console.log(apiDir, i); 69 | if (i.includes(apiDir)) { 70 | delete require.cache[require.resolve(i)]; 71 | } 72 | }); 73 | } 74 | 75 | // 导出服务器app 76 | module.exports = (app) => { 77 | // 解析post数据 78 | app.use(express.json()); 79 | app.use( 80 | express.urlencoded({ 81 | extended: true, 82 | }) 83 | ); 84 | 85 | // 注册路由表到app上 86 | const mockRoutes = registerRoutes(app); 87 | let mockRoutesLength = mockRoutes.mockRoutesLength; 88 | let mockStartIndex = mockRoutes.mockStartIndex; 89 | //* 观察mock下的文件变化(不包括mock-server.js),热更新文件,这样添加数据路由就不用重启了 90 | chokidar 91 | .watch(apiDir, { 92 | ignored: /mock-server/, 93 | ignoreInitial: true, 94 | }) 95 | .on("all", (event, path) => { 96 | try { 97 | // 先移除之前的路由 98 | app._router.stack.splice(mockStartIndex, mockRoutesLength); 99 | // 清除缓冲 100 | unregisterRoutes(); 101 | // 重新注册路由 102 | const mockRoutes = registerRoutes(app); 103 | mockRoutesLength = mockRoutes.mockRoutesLength; 104 | mockStartIndex = mockRoutes.mockStartIndex; 105 | console.log( 106 | chalk.magentaBright(`\n > 接口更新成功 --> 详情 ${path}`) 107 | ); 108 | } catch (err) { 109 | console.log(chalk.redBright(err)); 110 | } 111 | }); 112 | }; 113 | -------------------------------------------------------------------------------- /src/api/modules/system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-02-07 13:40:50 4 | * @LastEditTime: 2021-12-02 15:59:52 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\api\modules\system.js 8 | */ 9 | // const Mock = require("mockjs"); //引入 10 | /** 11 | * @description: 列表查询 12 | * @param {*} 13 | * @return {*} 14 | */ 15 | const searchList = (sRole, sLimit, sPage, sList, sName) => { 16 | let list = sList; 17 | if (sRole !== null && sRole !== "" && sRole !== undefined) { 18 | list = sList.filter((item) => item[sName] + "" === sRole + ""); 19 | } 20 | const res = {}; 21 | res.total = list.length; 22 | res.page = sPage * 1; 23 | res.limit = sLimit * 1; 24 | res.list = list.slice((sPage - 1) * sLimit, (sPage - 1) * sLimit + sLimit); 25 | return { 26 | message: "查询成功!", 27 | code: "00", 28 | data: res, 29 | }; 30 | }; 31 | /** 32 | * @description: 列表添加 33 | * @param {*} 34 | * @return {*} 35 | */ 36 | const addList = (opt, list, name, sName) => { 37 | const flag = list.some((item) => { 38 | if (item.type != "undefined") { 39 | if (item.type == 2) { 40 | return false; 41 | } 42 | } 43 | return item[sName] == name; 44 | }); 45 | let res = { 46 | message: "添加成功!", 47 | code: "00", 48 | }; 49 | if (flag) { 50 | res = { 51 | message: "名称已存在", 52 | code: "01", 53 | }; 54 | } else { 55 | let item = { 56 | id: list[list.length - 1].id + 1, 57 | ...opt.body, 58 | }; 59 | list.push(item); 60 | } 61 | return res; 62 | }; 63 | /** 64 | * @description:编辑列表 65 | * @param {*} 66 | * @return {*} 67 | */ 68 | const editList = (opt, list) => { 69 | const { 70 | body: { id }, 71 | } = opt; 72 | if (id == -1) { 73 | return { 74 | message: "系统超级管理员账户不能编辑!", 75 | code: "01", 76 | }; 77 | } 78 | const index = list.indexOf(list.find((item) => item.id + "" === id + "")); 79 | list.fill(opt.body, index, index + 1); 80 | return { 81 | message: "编辑成功!", 82 | code: "00", 83 | }; 84 | }; 85 | /** 86 | * @description:删除列表 87 | * @param {*} 88 | * @return {*} 89 | */ 90 | const delList = (opt, list) => { 91 | const { 92 | body: { id }, 93 | } = opt; 94 | if (id == -1) { 95 | return { 96 | message: "系统超级管理员账户不能删除!", 97 | code: "01", 98 | }; 99 | } 100 | const index = list.indexOf(list.find((item) => item.id + "" === id + "")); 101 | list.splice(index, 1); 102 | return { 103 | message: "删除成功!", 104 | code: "00", 105 | }; 106 | }; 107 | const menuList = [ 108 | { 109 | parentId: -1, 110 | id: 100, 111 | name: "系统设置", //看官网,这个名字是3-5之间的 112 | url: "", 113 | menu: "", 114 | type: 0, 115 | icon: "Setting", 116 | sort: 1, 117 | iframe: 1, 118 | }, 119 | { 120 | parentId: 100, 121 | id: 1, 122 | name: "用户管理", //看官网,这个名字是3-5之间的 123 | url: "system/Users", //这个类似上面的id一个,只是初始值是从100开始的 124 | menu: "", 125 | type: 1, 126 | icon: "UserFilled", 127 | sort: 2, 128 | iframe: 0, 129 | }, 130 | { 131 | parentId: 1, 132 | id: 2, 133 | name: "查询", //看官网,这个名字是3-5之间的 134 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 135 | menu: "search", 136 | type: 2, 137 | icon: "", 138 | sort: 1, 139 | iframe: 1, 140 | }, 141 | { 142 | parentId: 1, 143 | id: 3, 144 | name: "添加", //看官网,这个名字是3-5之间的 145 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 146 | menu: "add", 147 | type: 2, 148 | icon: "", 149 | sort: 1, 150 | iframe: 1, 151 | }, 152 | { 153 | parentId: 1, 154 | id: 4, 155 | name: "编辑", //看官网,这个名字是3-5之间的 156 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 157 | menu: "edit", 158 | type: 2, 159 | icon: "", 160 | sort: 1, 161 | iframe: 1, 162 | }, 163 | { 164 | parentId: 100, 165 | id: 5, 166 | name: "菜单管理", //看官网,这个名字是3-5之间的 167 | url: "system/Menus", //这个类似上面的id一个,只是初始值是从100开始的 168 | menu: "", 169 | type: 1, 170 | icon: "Menu", 171 | sort: 1, 172 | iframe: 0, 173 | }, 174 | { 175 | parentId: 5, 176 | id: 6, 177 | name: "查询", //看官网,这个名字是3-5之间的 178 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 179 | menu: "search", 180 | type: 2, 181 | icon: "", 182 | sort: 1, 183 | iframe: 1, 184 | }, 185 | { 186 | parentId: 5, 187 | id: 7, 188 | name: "添加", //看官网,这个名字是3-5之间的 189 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 190 | menu: "add", 191 | type: 2, 192 | icon: "", 193 | sort: 1, 194 | iframe: 1, 195 | }, 196 | { 197 | parentId: 5, 198 | id: 8, 199 | name: "编辑", //看官网,这个名字是3-5之间的 200 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 201 | menu: "edit", 202 | type: 2, 203 | icon: "", 204 | sort: 1, 205 | iframe: 1, 206 | }, 207 | { 208 | parentId: 5, 209 | id: 9, 210 | name: "添加子级", //看官网,这个名字是3-5之间的 211 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 212 | menu: "addChild", 213 | type: 2, 214 | icon: "", 215 | sort: 1, 216 | iframe: 1, 217 | }, 218 | { 219 | parentId: 5, 220 | id: 10, 221 | name: "添加按钮", //看官网,这个名字是3-5之间的 222 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 223 | menu: "addBtn", 224 | type: 2, 225 | icon: "", 226 | sort: 1, 227 | iframe: 1, 228 | }, 229 | { 230 | parentId: 100, 231 | id: 11, 232 | name: "角色管理", //看官网,这个名字是3-5之间的 233 | url: "system/Roles", //这个类似上面的id一个,只是初始值是从100开始的 234 | menu: "", 235 | type: 1, 236 | icon: "HelpFilled", 237 | sort: 3, 238 | iframe: 0, 239 | }, 240 | { 241 | parentId: 11, 242 | id: 12, 243 | name: "查询", //看官网,这个名字是3-5之间的 244 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 245 | menu: "search", 246 | type: 2, 247 | icon: "", 248 | sort: 1, 249 | iframe: 1, 250 | }, 251 | { 252 | parentId: 11, 253 | id: 13, 254 | name: "添加", //看官网,这个名字是3-5之间的 255 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 256 | menu: "add", 257 | type: 2, 258 | icon: "", 259 | sort: 1, 260 | iframe: 1, 261 | }, 262 | { 263 | parentId: 11, 264 | id: 14, 265 | name: "编辑", //看官网,这个名字是3-5之间的 266 | url: "", //这个类似上面的id一个,只是初始值是从100开始的 267 | menu: "edit", 268 | type: 2, 269 | icon: "", 270 | sort: 1, 271 | iframe: 1, 272 | }, 273 | { 274 | parentId: -1, 275 | id: 15, 276 | name: "参考资料", //看官网,这个名字是3-5之间的 277 | url: "", 278 | menu: "", 279 | type: 0, 280 | icon: "DocumentCopy", 281 | sort: 1, 282 | iframe: 1, 283 | }, 284 | { 285 | parentId: 15, 286 | id: 16, 287 | name: "vue3.0", //看官网,这个名字是3-5之间的 288 | url: "https://www.vue3js.cn/docs/zh/", //这个类似上面的id一个,只是初始值是从100开始的 289 | menu: "", 290 | type: 1, 291 | icon: "Promotion", 292 | sort: 1, 293 | iframe: 1, 294 | }, 295 | { 296 | parentId: 15, 297 | id: 17, 298 | name: "element-plus", //看官网,这个名字是3-5之间的 299 | url: "https://element-plus.org/#/zh-CN", //这个类似上面的id一个,只是初始值是从100开始的 300 | menu: "", 301 | type: 1, 302 | icon: "ElemeFilled", 303 | sort: 1, 304 | iframe: 1, 305 | }, 306 | ]; 307 | const userList = [ 308 | { 309 | id: -1, 310 | name: "Administrator", 311 | userName: "超级管理员", 312 | password: "123456", 313 | role: -1, 314 | status: 1, 315 | }, 316 | { 317 | id: 0, 318 | name: "admin", 319 | userName: "管理员", 320 | password: "123456", 321 | role: 0, 322 | status: 1, 323 | }, 324 | ]; 325 | const roleList = [ 326 | { 327 | id: -1, 328 | name: "super", 329 | roleName: "超级管理员", 330 | status: "1", 331 | role: menuList, 332 | }, 333 | { 334 | id: 0, 335 | name: "ceshi", 336 | roleName: "测试", 337 | status: "0", 338 | role: menuList, 339 | }, 340 | ]; 341 | 342 | const user = { 343 | name: "", 344 | userId: "", 345 | roleId: "", 346 | menus: null, 347 | }; 348 | 349 | module.exports = { 350 | userList: { 351 | url: "/user/list", 352 | type: "post", 353 | mock: true, 354 | response: (opt) => { 355 | const { 356 | body: { role, limit, page }, 357 | } = opt; 358 | return searchList(role, limit, page, userList, "role"); 359 | }, 360 | }, 361 | userAdd: { 362 | url: "/user/add", 363 | type: "post", 364 | mock: true, 365 | response: (opt) => { 366 | const { 367 | body: { name }, 368 | } = opt; 369 | return addList(opt, userList, name, "name"); 370 | }, 371 | }, 372 | userEdit: { 373 | url: "/user/edit", 374 | type: "post", 375 | mock: true, 376 | response: (opt) => { 377 | return editList(opt, userList); 378 | }, 379 | }, 380 | userDel: { 381 | url: "/user/del", 382 | type: "post", 383 | mock: true, 384 | response: (opt) => { 385 | return delList(opt, userList); 386 | }, 387 | }, 388 | userStatus: { 389 | url: "/user/status", 390 | type: "post", 391 | mock: true, 392 | response: (opt) => { 393 | const { 394 | body: { id, status }, 395 | } = opt; 396 | if (id == -1) { 397 | return { 398 | message: "系统超级管理员账户不能停用!", 399 | code: "01", 400 | }; 401 | } 402 | userList.find((item) => item.id == id).status = status; 403 | return { 404 | message: "切换成功!", 405 | code: "00", 406 | }; 407 | }, 408 | }, 409 | 410 | menuList: { 411 | url: "/menu/list", 412 | type: "post", 413 | mock: true, 414 | response: (opt) => { 415 | const { 416 | body: { name }, 417 | } = opt; 418 | let list = menuList; 419 | if (name) { 420 | list = menuList.filter((item) => item.name == name); 421 | } 422 | return { 423 | message: "查询成功!", 424 | code: "00", 425 | data: list, 426 | }; 427 | }, 428 | }, 429 | menuAdd: { 430 | url: "/menu/add", 431 | type: "post", 432 | mock: true, 433 | response: (opt) => { 434 | const { 435 | body: { name }, 436 | } = opt; 437 | return addList(opt, menuList, name, "name"); 438 | }, 439 | }, 440 | menuEdit: { 441 | url: "/menu/edit", 442 | type: "post", 443 | mock: true, 444 | response: (opt) => { 445 | return editList(opt, menuList); 446 | }, 447 | }, 448 | menuDel: { 449 | url: "/menu/del", 450 | type: "post", 451 | mock: true, 452 | response: (opt) => { 453 | return delList(opt, menuList); 454 | }, 455 | }, 456 | 457 | roleList: { 458 | url: "/role/list", 459 | type: "post", 460 | mock: true, 461 | response: (opt) => { 462 | const { 463 | body: { name, limit, page }, 464 | } = opt; 465 | return searchList(name, limit, page, roleList, "name"); 466 | }, 467 | }, 468 | roleAdd: { 469 | url: "/role/add", 470 | type: "post", 471 | mock: true, 472 | response: (opt) => { 473 | const { 474 | body: { name }, 475 | } = opt; 476 | return addList(opt, roleList, name, "name"); 477 | }, 478 | }, 479 | roleEdit: { 480 | url: "/role/edit", 481 | type: "post", 482 | mock: true, 483 | response: (opt) => { 484 | return editList(opt, roleList); 485 | }, 486 | }, 487 | roleDel: { 488 | url: "/role/del", 489 | type: "post", 490 | mock: true, 491 | response: (opt) => { 492 | return delList(opt, roleList); 493 | }, 494 | }, 495 | 496 | userMenuList: { 497 | url: "/userMenuList", 498 | type: "post", 499 | mock: true, 500 | response: () => { 501 | user.menus = roleList.find((item) => item.id == user.roleId).role; 502 | return { 503 | message: "查询成功!", 504 | code: "00", 505 | list: user.menus, 506 | }; 507 | }, 508 | }, 509 | login: { 510 | url: "/login", 511 | type: "post", 512 | mock: true, 513 | response: (opt) => { 514 | const { 515 | body: { userName, pwd }, 516 | } = opt; 517 | let data = { 518 | code: "00", 519 | message: "登录成功!", 520 | token: new Date().getTime(), 521 | uname: userName, 522 | }; 523 | user.name = userName; 524 | let _user = userList.find((item) => item.name == user.name); 525 | if (!_user) { 526 | debugger; 527 | data = { 528 | code: "01", 529 | message: "账户不存在", 530 | }; 531 | } else { 532 | if (_user.password !== pwd) { 533 | data = { 534 | code: "01", 535 | message: "密码错误", 536 | }; 537 | } else { 538 | if (_user.status == 0) { 539 | data = { 540 | code: "01", 541 | message: "账户停用", 542 | }; 543 | } else { 544 | user.userId = _user.id; 545 | user.roleId = _user.role; 546 | } 547 | } 548 | } 549 | return data; 550 | }, 551 | }, 552 | }; 553 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qq929323125/vue3-element-admin/dc1015714cf4bbd82bd3f590e588a528d932a13b/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Common.vue: -------------------------------------------------------------------------------- 1 | 9 | 39 | 40 | 335 | 336 | 509 | -------------------------------------------------------------------------------- /src/components/FunctionPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 26 | 27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/dashboard/LiveChart.vue: -------------------------------------------------------------------------------- 1 | 9 | 16 | 17 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/components/dashboard/Shortcuts.vue: -------------------------------------------------------------------------------- 1 | 9 | 59 | 60 | 61 | 62 | 123 | -------------------------------------------------------------------------------- /src/components/layout/NavigateBar.vue: -------------------------------------------------------------------------------- 1 | 9 | 16 | 21 | 28 | -------------------------------------------------------------------------------- /src/components/layout/SideBar.vue: -------------------------------------------------------------------------------- 1 | 9 | 33 | 34 | 60 | 61 | 70 | -------------------------------------------------------------------------------- /src/components/layout/components/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 9 | 17 | 18 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/layout/components/Hamburger.vue: -------------------------------------------------------------------------------- 1 | 9 | 16 | 17 | 27 | 28 | 40 | -------------------------------------------------------------------------------- /src/components/layout/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 26 | 27 | 34 | 35 | 66 | -------------------------------------------------------------------------------- /src/components/layout/components/Personal.vue: -------------------------------------------------------------------------------- 1 | 9 | 67 | 68 | 87 | 88 | 101 | -------------------------------------------------------------------------------- /src/components/layout/components/SlideMenu.vue: -------------------------------------------------------------------------------- 1 | 9 | 39 | 40 | 79 | 80 | 94 | -------------------------------------------------------------------------------- /src/components/veBaseComponents/VeTable.vue: -------------------------------------------------------------------------------- 1 | 9 | 58 | 59 | 126 | 127 | 142 | -------------------------------------------------------------------------------- /src/components/veBaseComponents/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-08-12 17:08:17 4 | * @LastEditTime: 2021-08-12 17:21:34 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\components\veBaseComponents\index.js 8 | */ 9 | export default { 10 | install: (app) => { 11 | const files = require.context( 12 | "@/components/veBaseComponents", 13 | false, 14 | /\.vue$/ 15 | ); 16 | files.keys().forEach((key) => { 17 | // 获取组件配置 18 | const componentConfig = files(key); 19 | // 全局注册组件 20 | app.component( 21 | componentConfig.default.name, 22 | componentConfig.default 23 | ); 24 | }); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-06 17:24:12 4 | * @LastEditTime: 2021-11-30 15:34:54 5 | * @LastEditors: Please set LastEditors 6 | * @Description: 修改配置需重启服务后生效 7 | * @FilePath: \vue3-element-admin\src\config.js 8 | */ 9 | module.exports = { 10 | dev_mock: true, //开发环境启用mock true:启用 11 | pro_mock: true, //生产环境启用mock true:启用 12 | }; 13 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-03-02 15:29:31 4 | * @LastEditTime: 2021-03-23 17:53:43 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\plugins\mock.js 8 | */ 9 | export default { 10 | install: (app, { router, store }) => { 11 | const files = require.context("@/directives/modules", false, /\.js$/); 12 | files.keys().forEach((key) => { 13 | let name = key.replace(/(\.\/|\.js)/g, ""); 14 | let method = files(key).default; 15 | app.directive(name, (el, binding) => 16 | method(el, binding, app, router, store) 17 | ); 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/directives/modules/permission.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-03-12 14:10:03 4 | * @LastEditTime: 2021-03-26 09:04:37 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\directives\modules\permission.js 8 | */ 9 | 10 | const permission = (el, binding, app, router, store) => { 11 | const { value } = binding; 12 | function checkArray(permission) { 13 | let path = app.config.globalProperties.$route.name; 14 | 15 | let _permission = permission.map((element) => { 16 | let url = path.replace(/-/g, "/") + "/" + element; 17 | return url; 18 | }); 19 | let arr = store.getters.permissionList; 20 | return _permission.some((key) => arr.includes(key)); 21 | } 22 | 23 | if (value && value.length > 0) { 24 | let hasPermission = checkArray(value); 25 | if (!hasPermission) { 26 | // 没有权限 移除Dom元素 27 | el.parentNode && el.parentNode.removeChild(el); 28 | } 29 | } 30 | }; 31 | 32 | export default permission; 33 | -------------------------------------------------------------------------------- /src/directives/modules/resize.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-03-17 09:30:54 4 | * @LastEditTime: 2022-01-20 14:24:15 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\directives\modules\resize.js 8 | */ 9 | import { useElementSize, debouncedWatch } from "@vueuse/core"; 10 | const resize = (el, binding) => { 11 | const { width } = useElementSize(el); 12 | if (width.value === 0) return; 13 | const { value } = binding; 14 | debouncedWatch( 15 | width, 16 | () => { 17 | value && value.resize(); 18 | }, 19 | { debounce: 500 } 20 | ); 21 | }; 22 | export default resize; 23 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-10-14 13:50:09 4 | * @LastEditTime: 2022-01-27 15:30:41 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Ed 7 | * @FilePath: \vue3-element-admin\src\main.js 8 | */ 9 | import { createApp } from "vue"; 10 | import axios from "@/plugins/axios"; 11 | import App from "@/App.vue"; 12 | import router from "@/router"; 13 | import store from "@/store"; 14 | import installElementPlus from "@/plugins/element"; 15 | import elementIcon from "@/plugins/svgicon"; 16 | import permission from "@/plugins/permission"; 17 | import mock from "@/plugins/mock"; 18 | import directives from "@/directives"; 19 | import veBaseComponents from "@/components/veBaseComponents"; 20 | 21 | import "normalize.css/normalize.css"; 22 | import "nprogress/nprogress.css"; 23 | import "@/styles/common.scss"; 24 | const app = createApp(App); 25 | app.use(mock) 26 | .use(elementIcon) 27 | .use(veBaseComponents) 28 | .use(store) 29 | .use(router) 30 | .use(installElementPlus) 31 | .use(axios, { router, store, opt: "VE_API" }) 32 | .use(permission, { router, store }) 33 | .use(directives, { router, store }) 34 | .mount("#app"); 35 | -------------------------------------------------------------------------------- /src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | /* 3 | * @Author: your name 4 | * @Date: 2020-10-16 10:38:49 5 | * @LastEditTime: 2021-12-02 15:50:51 6 | * @LastEditors: Please set LastEditors 7 | * @Description: In User Settings Edit 8 | * @FilePath: \vue3-element-admin\src\plugins\axios.js 9 | */ 10 | "use strict"; 11 | 12 | import axios from "axios"; 13 | import Qs from "qs"; 14 | import NProgress from "nprogress"; 15 | import { SET_TOKEN } from "@/store/modules/app/type"; 16 | 17 | // Full config: https://github.com/axios/axios#request-config 18 | // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || ''; 19 | // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; 20 | // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 21 | const install = (app, { router, store, opt }) => { 22 | let config = { 23 | Global: true, 24 | // baseURL: process.env.baseURL || process.env.apiUrl || "" 25 | // timeout: 60 * 1000, // Timeout 26 | // withCredentials: true, // Check cross-site Access-Control 27 | // {"userName":"Administrator","pwd":"123456"} 28 | // userName=Administrator&pwd=123456 29 | }; 30 | if (VE_ENV.MODE === "production") { 31 | config.transformRequest = [(data) => Qs.parse(data)]; 32 | } 33 | 34 | const _axios = axios.create(config); 35 | let ve_loading; 36 | let ve_message = null; 37 | let loadingCount = 0; 38 | // 请求拦截 39 | _axios.interceptors.request.use( 40 | (config) => { 41 | console.log(config); 42 | 43 | NProgress.done(); 44 | if (config.Global) { 45 | NProgress.start(); 46 | ve_loading = app.config.globalProperties.$loading({ 47 | lock: true, 48 | text: "Loading", 49 | spinner: "el-icon-loading", 50 | background: "rgba(0,0,0,0.1)", 51 | }); 52 | } 53 | loadingCount++; 54 | //*请求头添加token 55 | const token = store.getters.token; 56 | token && (config.headers.Authorization = token); 57 | 58 | // Do something before request is sent 59 | return config; 60 | }, 61 | (error) => { 62 | // Do something with request error 63 | return Promise.reject(error); 64 | } 65 | ); 66 | 67 | // Add a response interceptor 68 | // 响应拦截 69 | _axios.interceptors.response.use( 70 | (response) => { 71 | // TODO 根据响应头更新token 72 | store.dispatch(`app/${SET_TOKEN}`, new Date().getTime()); 73 | 74 | loadingCount--; 75 | if (loadingCount <= 0) { 76 | NProgress.done(); 77 | ve_loading.close(); 78 | } 79 | 80 | let type = "success"; 81 | if (response.data.code != "00") { 82 | type = "error"; 83 | } 84 | if (ve_message) { 85 | ve_message.close(); 86 | ve_message = null; 87 | } 88 | ve_message = app.config.globalProperties.$message({ 89 | type, 90 | message: response.data.message, 91 | }); 92 | // Do something with response data 93 | return response.data; 94 | }, 95 | (error) => { 96 | loadingCount--; 97 | if (loadingCount <= 0) { 98 | NProgress.done(); 99 | ve_loading.close(); 100 | } 101 | if (error && error.response) { 102 | let message = ""; 103 | switch (error.response.status) { 104 | case 400: 105 | message = "请求错误"; 106 | break; 107 | case 401: { 108 | message = "未授权,请登录"; 109 | router.replace({ 110 | name: "Login", 111 | }); 112 | break; 113 | } 114 | case 403: 115 | message = "没有权限,拒绝访问"; 116 | break; 117 | case 404: 118 | message = `请求地址出错`; 119 | break; 120 | case 408: 121 | message = "请求超时"; 122 | break; 123 | case 500: 124 | message = "服务器内部错误"; 125 | break; 126 | case 501: 127 | message = "服务未实现"; 128 | break; 129 | case 502: 130 | message = "网关错误"; 131 | break; 132 | case 503: 133 | message = "服务不可用"; 134 | break; 135 | case 504: 136 | message = "网关超时"; 137 | break; 138 | case 505: 139 | message = "HTTP版本不受支持"; 140 | break; 141 | default: 142 | break; 143 | } 144 | if (ve_message) { 145 | ve_message.close(); 146 | ve_message = null; 147 | } 148 | ve_message = app.config.globalProperties.$message({ 149 | message, 150 | type: "error", 151 | }); 152 | } 153 | // Do something with response error 154 | return Promise.reject(error); 155 | } 156 | ); 157 | 158 | const method = { 159 | post: (url, p, config) => _axios.post(url, p, config), 160 | get: (url, p, config) => 161 | _axios.get(url, Object.assign(config, { params: p })), 162 | }; 163 | 164 | let api = {}; 165 | const files = require.context("@/api/modules", false, /\.js$/); 166 | files.keys().forEach((key) => { 167 | const fileName = key.replace(/(\.\/|\.js)/g, ""); 168 | api[fileName] = {}; 169 | let obj = files(key); 170 | Object.keys(obj).forEach((item) => { 171 | api[fileName][item] = (p, config = {}) => 172 | method[obj[item].type](obj[item].url, p, config); 173 | }); 174 | }); 175 | 176 | window[opt] = api; 177 | app.config.globalProperties[opt] = api; 178 | }; 179 | 180 | export default { install }; 181 | -------------------------------------------------------------------------------- /src/plugins/element.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 10:51:26 4 | * @LastEditTime: 2022-01-20 10:40:41 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\plugins\element.js 8 | */ 9 | 10 | import ElementPlus from "element-plus"; 11 | import "element-plus/dist/index.css"; 12 | import zhCn from "element-plus/es/locale/lang/zh-cn"; 13 | import "dayjs/locale/zh-cn"; 14 | export default { 15 | install: (app) => { 16 | app.use(ElementPlus, { 17 | locale: zhCn, 18 | size: "default", 19 | }); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/plugins/mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-03-02 15:29:31 4 | * @LastEditTime: 2021-08-09 15:29:58 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit1 7 | * @FilePath: \vue3-element-admin\src\plugins\mock.js 8 | */ 9 | export default { 10 | install: () => { 11 | const config = require("@/config"); 12 | if (config.pro_mock && VE_ENV.MODE === "production") { 13 | const Mock = require("mockjs"); //引入 14 | 15 | const files = require.context("@/api/modules", false, /\.js$/); 16 | files.keys().forEach((key) => { 17 | let obj = files(key); 18 | Object.keys(obj).forEach((item) => { 19 | Mock.mock( 20 | obj[item].url, 21 | obj[item].type, 22 | obj[item].response 23 | ); 24 | }); 25 | }); 26 | } 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/plugins/permission.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-13 17:32:55 4 | * @LastEditTime: 2021-12-02 17:02:24 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\plugins\permission.js 8 | */ 9 | import { SET_MENU_LIST, SET_PERMISSION_LIST } from "@/store/modules/app/type"; 10 | 11 | import globalRoutes from "@/router/globalRoutes"; 12 | import mainRoutes from "@/router/mainRoutes"; 13 | import NProgress from "nprogress"; 14 | 15 | /** 16 | * 判断当前路由类型, global: 全局路由, main: 主入口路由 17 | * @param {*} route 当前路由 18 | */ 19 | function fnCurrentRouteType(route, globalRoutes = []) { 20 | let temp = []; 21 | for (let i = 0; i < globalRoutes.length; i++) { 22 | if (route.name === globalRoutes[i].name) { 23 | return "global"; 24 | } else if ( 25 | globalRoutes[i].children && 26 | globalRoutes[i].children.length >= 1 27 | ) { 28 | temp = temp.concat(globalRoutes[i].children); 29 | } 30 | } 31 | return temp.length >= 1 ? fnCurrentRouteType(route, temp) : "main"; 32 | } 33 | 34 | export default { 35 | install: (app, { router, store }) => { 36 | // let router = opt; 37 | router.beforeEach(async (to, from, next) => { 38 | const token = store.getters.token; 39 | if ( 40 | router.options.isAddDynamicMenuRoutes || 41 | fnCurrentRouteType(to, globalRoutes) === "global" 42 | ) { 43 | //* 1. 已经添加 or 全局路由, 直接访问 44 | if (to.meta.title) { 45 | document.title = to.meta.title; 46 | } 47 | NProgress.start(); 48 | next(); 49 | } else { 50 | // let token = sessionStorage.getItem("token"); 51 | if (!token || !/\S/.test(token)) { 52 | next({ name: "Login" }); 53 | } else { 54 | let data = await VE_API.system.userMenuList(); 55 | if (data && data.code === "00") { 56 | let _list = XE.clone(data.list, true); 57 | data.list = XE.mapTree( 58 | XE.toArrayTree(_list, { 59 | sortKey: "sort", 60 | }), 61 | (item) => { 62 | if ( 63 | item.children && 64 | item.children.length <= 0 65 | ) { 66 | delete item.children; 67 | } 68 | return item; 69 | } 70 | ); 71 | await fnAddDynamicMenuRoutes(data.list); 72 | router.options.isAddDynamicMenuRoutes = true; 73 | await store.dispatch(`app/${SET_MENU_LIST}`, data.list); 74 | await store.dispatch( 75 | `app/${SET_PERMISSION_LIST}`, 76 | data.list 77 | ); 78 | NProgress.start(); 79 | next({ ...to, replace: true }); 80 | } else { 81 | next({ name: "Login" }); 82 | } 83 | } 84 | } 85 | }); 86 | router.afterEach(() => { 87 | NProgress.done(); 88 | }); 89 | 90 | /** 91 | * 添加动态(菜单)路由 92 | * @param {*} menuList 菜单列表 93 | * @param {*} routes 递归创建的动态(菜单)路由 94 | */ 95 | // eslint-disable-next-line no-unused-vars 96 | const fnAddDynamicMenuRoutes = async (menuList = [], routes = []) => { 97 | let temp = []; 98 | for (let i = 0; i < menuList.length; i++) { 99 | if ( 100 | menuList[i].type == 0 && 101 | menuList[i].children && 102 | menuList[i].children.length >= 1 103 | ) { 104 | temp = temp.concat(menuList[i].children); 105 | } else if (menuList[i].type == 1) { 106 | // } else if (menuList[i].type==1 && /\S/.test(menuList[i].url)) { 107 | // const url = menuList[i].url.replace(/\//g, "_"); 108 | let route = { 109 | path: 110 | menuList[i].url.replace(/\//g, "-") + 111 | `-${menuList[i].id}`, 112 | component: null, 113 | name: 114 | menuList[i].url.replace(/\//g, "-") + 115 | `-${menuList[i].id}`, 116 | // meta: { 117 | // } 118 | }; 119 | // url以http[s]://开头, 通过iframe展示 120 | if (menuList[i].iframe == 1) { 121 | route["path"] = `i-${menuList[i].id}`; 122 | route["name"] = `i-${menuList[i].id}`; 123 | route["props"] = { url: menuList[i].url }; 124 | route["component"] = () => import("@/views/IFrame.vue"); 125 | } else { 126 | const l = "views/layoutpages/" + menuList[i].url; 127 | route["component"] = () => import("@/" + l + ".vue"); 128 | } 129 | routes.push(route); 130 | } 131 | } 132 | if (temp.length >= 1) { 133 | fnAddDynamicMenuRoutes(temp, routes); 134 | } else { 135 | mainRoutes.children = mainRoutes.children.concat(routes); 136 | // mainRoutes.children = routes; 137 | console.log( 138 | "控制台打印--> ~ file: permission.js ~ line 127 ~ fnAddDynamicMenuRoutes ~ mainRoutes.children", 139 | mainRoutes.children 140 | ); 141 | 142 | await router.addRoute(mainRoutes); 143 | await router.addRoute({ 144 | path: "/:w+", 145 | redirect: { name: "404" }, 146 | }); 147 | } 148 | }; 149 | }, 150 | }; 151 | -------------------------------------------------------------------------------- /src/plugins/svgicon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-08-13 15:48:48 4 | * @LastEditTime: 2022-01-20 10:35:29 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\plugins\svgicon.js 8 | */ 9 | import * as components from "@element-plus/icons-vue"; 10 | export default { 11 | install: (app) => { 12 | for (const key in components) { 13 | const componentConfig = components[key]; 14 | app.component(componentConfig.name, componentConfig); 15 | } 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/router/globalRoutes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-13 17:37:21 4 | * @LastEditTime: 2021-01-18 09:45:13 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\router\globalRoutes.js 8 | */ 9 | export default [ 10 | { 11 | path: "/login", 12 | name: "Login", 13 | component: () => import("@/views/Login.vue"), 14 | }, 15 | { 16 | path: "/404", 17 | name: "404", 18 | component: () => import("@/views/404.vue"), 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 11:41:32 4 | * @LastEditTime: 2021-03-24 16:14:18 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\router\index.js 8 | */ 9 | import { createRouter, createWebHashHistory } from "vue-router"; 10 | import globalRoutes from "./globalRoutes"; 11 | import mainRoutes from "./mainRoutes"; 12 | 13 | const router = createRouter({ 14 | history: createWebHashHistory(), 15 | scrollBehavior: () => ({ y: 0 }), 16 | isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由 17 | routes: globalRoutes.concat(mainRoutes), 18 | }); 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/router/mainRoutes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-13 17:39:02 4 | * @LastEditTime: 2021-01-18 15:48:29 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\router\mainRoutes.js 8 | */ 9 | export default { 10 | path: "/", 11 | name: "AppMain", 12 | component: () => import("@/views/AppMain.vue"), 13 | redirect: { name: "Home" }, 14 | children: [ 15 | { 16 | path: "home", 17 | name: "Home", 18 | component: () => import("@/views/Home.vue"), 19 | }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 17:46:31 4 | * @LastEditTime: 2021-03-12 14:31:58 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\store\getters.js 8 | */ 9 | export default { 10 | opened: (state) => state.app.slider.opened, 11 | token: (state) => state.app.token, 12 | uname: (state) => state.app.uname, 13 | menuList: (state) => state.app.menuList, 14 | permissionList: (state) => state.app.permissionList, 15 | }; 16 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-07 11:41:32 4 | * @LastEditTime: 2021-12-02 17:08:16 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\store\index.js 8 | */ 9 | let modules = {}; 10 | const files = require.context("./modules", true, /index.js$/); 11 | files.keys().forEach((key) => { 12 | const fileName = key.split("/")[1]; 13 | modules[fileName] = files(key).default; 14 | }); 15 | 16 | import { createStore } from "vuex"; 17 | import getters from "./getters"; 18 | export default createStore({ 19 | getters, 20 | modules, 21 | }); 22 | -------------------------------------------------------------------------------- /src/store/modules/app/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-12-02 16:55:35 4 | * @LastEditTime: 2021-12-02 16:58:05 5 | * @LastEditors: Please set LastEditors 6 | * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 7 | * @FilePath: \vue3-element-admin\src\store\modules\app\index.js 8 | */ 9 | import { 10 | TOGGLE_SLIDER, 11 | SET_TOKEN, 12 | SET_UNAME, 13 | SET_MENU_LIST, 14 | SET_PERMISSION_LIST, 15 | } from "./type.js"; 16 | export default { 17 | namespaced: true, 18 | state: { 19 | slider: { 20 | opened: JSON.parse(sessionStorage.getItem("opened")), 21 | }, 22 | token: sessionStorage.getItem("token") || "", 23 | menuList: null, 24 | permissionList: [], 25 | uname: sessionStorage.getItem("uname") || "", 26 | }, 27 | mutations: { 28 | [TOGGLE_SLIDER](state) { 29 | state.slider.opened = !state.slider.opened; 30 | sessionStorage.setItem( 31 | "opened", 32 | JSON.stringify(state.slider.opened) 33 | ); 34 | }, 35 | [SET_TOKEN](state, token) { 36 | state.token = token; 37 | sessionStorage.setItem("token", state.token); 38 | }, 39 | [SET_UNAME](state, uname) { 40 | state.uname = uname; 41 | sessionStorage.setItem("uname", state.uname); 42 | }, 43 | [SET_MENU_LIST](state, menuList) { 44 | state.menuList = menuList; 45 | }, 46 | [SET_PERMISSION_LIST](state, permissionList) { 47 | state.permissionList = permissionList; 48 | }, 49 | }, 50 | actions: { 51 | [TOGGLE_SLIDER]({ commit }) { 52 | commit(TOGGLE_SLIDER); 53 | }, 54 | [SET_TOKEN]({ commit }, token) { 55 | commit(SET_TOKEN, token); 56 | }, 57 | [SET_UNAME]({ commit }, uname) { 58 | commit(SET_UNAME, uname); 59 | }, 60 | [SET_MENU_LIST]({ commit }, menuList) { 61 | commit(SET_MENU_LIST, menuList); 62 | }, 63 | [SET_PERMISSION_LIST]({ commit }, menuList) { 64 | let allMenus = XE.filterTree(menuList, (item) => item.type == 1); 65 | let permissionList = []; 66 | allMenus.forEach((item) => { 67 | if (item.children && item.children.length > 0) { 68 | item.children.forEach((menu) => { 69 | permissionList.push( 70 | `${item.url}/${item.id}/${menu.menu}` 71 | ); 72 | }); 73 | } 74 | }); 75 | commit(SET_PERMISSION_LIST, permissionList); 76 | }, 77 | }, 78 | }; 79 | -------------------------------------------------------------------------------- /src/store/modules/app/type.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-12-02 16:55:43 4 | * @LastEditTime: 2021-12-02 16:55:45 5 | * @LastEditors: Please set LastEditors 6 | * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 7 | * @FilePath: \vue3-element-admin\src\store\modules\app\type.js 8 | */ 9 | export const TOGGLE_SLIDER = "TOGGLE_SLIDER"; 10 | export const SET_TOKEN = "SET_TOKEN"; 11 | export const SET_UNAME = "SET_UNAME"; 12 | export const SET_MENU_LIST = "SET_MENU_LIST"; 13 | export const SET_PERMISSION_LIST = "SET_PERMISSION_LIST"; 14 | -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | html { 2 | background: #fff; 3 | transition: color 300ms, background-color 300ms; 4 | &.dark { 5 | filter: contrast(100%) invert(100%); 6 | img { 7 | filter: hue-rotate(180deg); 8 | } 9 | } 10 | } 11 | .ve_header_row_class_name, 12 | .el-table__fixed-right-patch { 13 | background: $main-bg-color !important; 14 | } 15 | .ve_header_cell_class_name { 16 | background: $main-bg-color !important; 17 | } 18 | .ve_cell_class_name { 19 | background: $base-color !important; 20 | border-color: $base-color !important; 21 | } 22 | .ve_row_class_name { 23 | background: $base-color !important; 24 | } 25 | .ve_p_10 { 26 | padding: 10px; 27 | } 28 | //滚动条的宽度 29 | ::-webkit-scrollbar { 30 | width: 6px; 31 | height: 6px; 32 | } 33 | //滚动条的滑块 34 | ::-webkit-scrollbar-thumb { 35 | background-color: rgba(144, 147, 153, 0.3); 36 | border-radius: 3px; 37 | } 38 | .ve_select_option_slot { 39 | font-size: 12px; 40 | float: left; 41 | line-height: initial; 42 | padding-bottom: 10px; 43 | opacity: 0.7; 44 | } 45 | .ve_option_box { 46 | width: calc(50% - 132px); 47 | } 48 | .size-watch { 49 | width: 100%; 50 | height: 100%; 51 | // display: none; 52 | position: absolute; 53 | top: 0; 54 | z-index: -1; 55 | visibility: hidden; 56 | margin: 0; 57 | padding: 0; 58 | border: 0; 59 | } 60 | 61 | .ve_flex_col { 62 | display: flex; 63 | flex-direction: column; 64 | height: calc(100vh - #{$nav-height} - 80px); 65 | } 66 | -------------------------------------------------------------------------------- /src/styles/variables.scss.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-08 10:11:02 4 | * @LastEditTime: 2021-02-09 09:33:20 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\styles\variables.scss.js 8 | */ 9 | const variables = { 10 | main_bg_color: "#f5f5f5", 11 | base_color: "#409EFF", 12 | nav_height: "50px", 13 | side_close_width: "65px", 14 | side_open_width: "160px", 15 | sideBgColor: "#545c64", 16 | sideTextColor: "#fff", 17 | sideActiveTextColor: "#ffd04b", 18 | }; 19 | module.exports = variables; 20 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-01-12 09:38:09 4 | * @LastEditTime: 2022-01-20 10:37:39 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\utils\index.js 8 | */ 9 | /** 10 | * @description:树形结构转一维数组 11 | * @param {*} nodes 12 | * @return {*} 13 | */ 14 | export function jsonToArray(nodes) { 15 | let pid = -1; 16 | const toArray = (nodes) => { 17 | let r = []; 18 | if (Array.isArray(nodes)) { 19 | for (let i = 0, l = nodes.length; i < l; i++) { 20 | nodes[i].pid = pid; 21 | r.push(nodes[i]); // 取每项数据放入一个新数组 22 | if ( 23 | Array.isArray(nodes[i]["children"]) && 24 | nodes[i]["children"].length > 0 25 | ) { 26 | // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children 27 | pid = nodes[i].id; 28 | r = r.concat(toArray(nodes[i]["children"])); 29 | delete nodes[i]["children"]; 30 | } 31 | } 32 | } 33 | return r; 34 | }; 35 | return toArray(nodes); 36 | } 37 | 38 | /** 39 | * @description:一维数组转树形结构 40 | * @param {*} treeArray 41 | * @return {*} 42 | */ 43 | export function arrayToJson(treeArray) { 44 | var r = []; 45 | var tmpMap = {}; 46 | for (var i = 0, l = treeArray.length; i < l; i++) { 47 | //* 以每条数据的id作为obj的key值,数据作为value值存入到一个临时对象里面 48 | tmpMap[treeArray[i]["id"]] = treeArray[i]; 49 | } 50 | for (i = 0, l = treeArray.length; i < l; i++) { 51 | var key = tmpMap[treeArray[i]["pid"]]; 52 | //*循环每一条数据的pid,假如这个临时对象有这个key值,就代表这个key对应的数据有children,需要Push进去 53 | //*如果这一项数据属于哪个数据的子级 54 | if (key) { 55 | // *如果这个数据没有children 56 | if (!key["children"]) { 57 | key["children"] = []; 58 | key["children"].push(treeArray[i]); 59 | //* 如果这个数据有children 60 | } else { 61 | key["children"].push(treeArray[i]); 62 | } 63 | } else { 64 | //*如果没有这个Key值,就代表找不到属于哪个数据,那就代表没有父级,直接放在最外层 65 | r.push(treeArray[i]); 66 | } 67 | } 68 | return r; 69 | } 70 | 71 | /** 72 | * @description 获取节点的所有父节点 73 | * @param {*} tree 74 | * @param {*} func 75 | * @param {*} path 76 | * @return {*} 77 | */ 78 | export const treeFindPath = (tree, func, name = "id", path = []) => { 79 | if (!tree) return []; 80 | for (const data of tree) { 81 | //* 这里按照你的需求来存放最后返回的内容吧 82 | path.push(data[name]); 83 | if (func(data)) return path; 84 | if (data.children) { 85 | const findChildren = treeFindPath(data.children, func, name, path); 86 | if (findChildren.length) return findChildren; 87 | } 88 | path.pop(); 89 | } 90 | return []; 91 | }; 92 | 93 | /** 94 | * @description: 拆箱函数,解决tooltip显示问题 95 | * @param {*} obj 96 | * @return {*} 97 | */ 98 | export const unwarp = (obj) => obj && (obj.__v_raw || obj.valueOf() || obj); 99 | 100 | /** 101 | * @description:获取所有的el-svg-icon组件名 102 | * @param {*} 103 | * @return {*} 104 | */ 105 | export const icons = () => { 106 | const components = require("@element-plus/icons-vue"); 107 | console.log("🚀 ~ file: index.js ~ line 107 ~ icons ~ e", components); 108 | 109 | const names = []; 110 | for (const key in components) { 111 | names.push(components[key].name); 112 | } 113 | return names; 114 | }; 115 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2019-11-21 18:09:12 4 | * @LastEditTime: 2021-01-14 10:11:56 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \shengshi\src\utils\validate.js 8 | */ 9 | /** 10 | * 邮箱 11 | * @param {* s 12 | */ 13 | export const isEmail = (s) => 14 | /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3){1,2)$/.test(s); 15 | 16 | /** 17 | * 手机号码 18 | * @param {* s 19 | */ 20 | export const isMobile = (s) => /^1[3-8][0-9]{9$/.test(s); 21 | 22 | /** 23 | * 电话号码 24 | * @param {* s 25 | */ 26 | export const isPhone = (s) => /^([0-9]{3,4-)?[0-9]{7,8$/.test(s); 27 | 28 | /** 29 | * URL地址 30 | * @param {* s 31 | */ 32 | export const isURL = (s) => /^http[s]?:\/\/.*/.test(s); 33 | 34 | /** 35 | * ip地址 36 | * @param {* s 37 | */ 38 | export const isIP = (s) => 39 | /^(25[0-5]|2[0-4]\d|[0-1]\d{2|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2|[1-9]?\d)$/.test( 40 | s 41 | ); 42 | 43 | /** 44 | * 字符串 45 | * @param {* s 46 | */ 47 | export const isString = (s) => /^[A-Za-z0-9_\-\u4e00-\u9fa5]+$/.test(s); 48 | 49 | /** 50 | * @description: 51 | * @param {type 52 | * @: exp 53 | * 非负浮点数字 54 | */ 55 | export const isNumber = (s) => /^\d+(\.\d+)?$/.test(s); 56 | 57 | /** 58 | * @description: 59 | * @param {type 60 | * @: 61 | * 银行卡正则 62 | */ 63 | 64 | export const isBank = (s) => 65 | /^([1-9]{1)(\d{11|\d{15|\d{16|\d{17|\d{18)$/.test(s); 66 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 35 | 51 | 60 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 9 | 20 | 21 | 25 | 26 | 46 | -------------------------------------------------------------------------------- /src/views/IFrame.vue: -------------------------------------------------------------------------------- 1 | 9 | 18 | 19 | 44 | 45 | 51 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 9 | 60 | 61 | 101 | 102 | 129 | -------------------------------------------------------------------------------- /src/views/layoutpages/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2021-02-07 17:11:28 4 | * @LastEditTime: 2021-08-18 17:51:13 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\src\views\layoutpages\common.js 8 | */ 9 | 10 | /** 11 | * @description:提交搜索 12 | * @param {*} 13 | * @return {*} 14 | */ 15 | export const onSubmit = (params, getDataList) => { 16 | params.limit = 10; 17 | params.page = 1; 18 | getDataList(); 19 | }; 20 | /** 21 | * @description:重置 22 | * @param {*} 23 | * @return {*} 24 | */ 25 | export const resetForm = (queryForm, params, getDataList) => { 26 | queryForm.resetFields(); 27 | onSubmit(params, getDataList); 28 | }; 29 | /** 30 | * @description:每页条数事件 31 | * @param {*} 32 | * @return {*} 33 | */ 34 | export const handleSizeChange = (val, params, getDataList) => { 35 | params.page = 1; 36 | params.limit = val; 37 | getDataList(); 38 | }; 39 | /** 40 | * @description:改变页数事件 41 | * @param {*} 42 | * @return {*} 43 | */ 44 | export const handleCurrentChange = (val, params, getDataList) => { 45 | params.page = val; 46 | getDataList(); 47 | }; 48 | 49 | /** 50 | * @description: 获取按钮跳转菜单的路径 51 | * @param {btnName} 跳转按钮的key值 52 | * @param {toPathUrl} 需要跳转到的菜单的路径 该路径为layoutpages下的文件子路径 53 | * @param {pathId} 当前页面的路由id 54 | * @param {menuList} 所有注册过的路由列表 55 | * @param {proxy} vue实例 56 | * @return {name} 跳转路由的name值 57 | */ 58 | export const findName = (btnName, toPathUrl, pathId, menuList, proxy) => { 59 | let toId = ""; 60 | let _item = XE.findTree(menuList, (item) => item.id == pathId); 61 | if ( 62 | _item && 63 | _item.item && 64 | _item.item.children && 65 | _item.item.children.length > 0 66 | ) { 67 | let btn = _item.item.children.find((item) => item.menu == btnName); 68 | 69 | btn && (toId = btn.toPath); 70 | } 71 | if (toId != "") { 72 | let _toItem = XE.findTree(menuList, (item) => item.id == toId); 73 | if (_toItem && _toItem.item) { 74 | if (_toItem.item.iframe == 0) { 75 | if (_toItem.item.url == toPathUrl) { 76 | return `${toPathUrl.replace(/\//g, "-")}-${toId}`; 77 | } 78 | } else { 79 | return `i-${toId}`; 80 | } 81 | } 82 | } 83 | proxy.$message({ 84 | type: "error", 85 | message: "无法跳转,请联系系统管理员!", 86 | }); 87 | }; 88 | 89 | /** 90 | * @description:根据权限动态添加路由 91 | * @param {title} 标题名称 92 | * @param {path} 组件路径 layoutpages下的组件路径 93 | * @param {name} 按钮key值 94 | * @param {{ router, route }} 路由对象 95 | * @return {_route.name} 返回注册后的name值 96 | */ 97 | export const getAsyncRouteName = async ( 98 | title, 99 | path, 100 | name, 101 | { router, route } 102 | ) => { 103 | const FunctionPage = require("@/components/FunctionPage.vue").default; 104 | const AsyncComponent = require("@/views/layoutpages/" + 105 | path + 106 | ".vue").default; 107 | // const { defineAsyncComponent } = require("vue"); 108 | // const AsyncComponent = defineAsyncComponent(() => 109 | // import("@/views/layoutpages/" + path + ".vue") 110 | // ); 111 | // import { defineAsyncComponent } from "vue"; 112 | // import FunctionPage from "@/components/FunctionPage"; 113 | 114 | const app = { 115 | components: { 116 | FunctionPage, 117 | AsyncComponent, 118 | }, 119 | data: () => ({ 120 | rName: null, 121 | }), 122 | methods: { 123 | reload(e) { 124 | return (e.returnValue = ""); 125 | }, 126 | }, 127 | mounted() { 128 | this.rName = this.$route.name; 129 | window.addEventListener("beforeunload", this.reload); 130 | }, 131 | beforeUnmount() { 132 | window.removeEventListener("beforeunload", this.reload); 133 | this.$router.removeRoute(this.rName); 134 | }, 135 | render() { 136 | return ( 137 | 138 | 139 | 140 | ); 141 | }, 142 | }; 143 | const _route = { 144 | name: route.name + "/" + name, 145 | path: route.name + "/" + name, 146 | 147 | component: app, 148 | }; 149 | await router.addRoute("AppMain", _route); 150 | return _route.name; 151 | }; 152 | -------------------------------------------------------------------------------- /src/views/layoutpages/leisure/Game.vue: -------------------------------------------------------------------------------- 1 | 8 | 84 | 85 | 92 | 93 | 196 | 197 | 263 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/Menus.vue: -------------------------------------------------------------------------------- 1 | 9 | 186 | 201 | 202 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/Roles.vue: -------------------------------------------------------------------------------- 1 | 9 | 111 | 125 | 126 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/UserTable.vue: -------------------------------------------------------------------------------- 1 | 9 | 151 | 164 | 165 | 313 | 314 | 315 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/Users.vue: -------------------------------------------------------------------------------- 1 | 9 | 152 | 165 | 166 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/components/MenuEdit.vue: -------------------------------------------------------------------------------- 1 | 9 | 194 | 195 | 553 | 554 | 555 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/components/RoleEdit.vue: -------------------------------------------------------------------------------- 1 | 9 | 81 | 82 | 247 | 248 | 263 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/components/UsersEdit.vue: -------------------------------------------------------------------------------- 1 | 9 | 76 | 77 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /src/views/layoutpages/system/components/UsersEditRoute.vue: -------------------------------------------------------------------------------- 1 | 9 | 69 | 70 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-10-14 15:24:16 4 | * @LastEditTime: 2022-01-20 11:38:21 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \vue3-element-admin\vue.config.js 8 | */ 9 | const config = require("./src/config"); 10 | const webpack = require("webpack"); 11 | const TerserPlugin = require("terser-webpack-plugin"); 12 | const CompressionWebpackPlugin = require("compression-webpack-plugin"); 13 | let scssVariables = require("./src/styles/variables.scss.js"); 14 | 15 | module.exports = { 16 | publicPath: "", 17 | productionSourceMap: false, 18 | 19 | devServer: { 20 | setupMiddlewares: (middlewares, devServer) => { 21 | if (config.dev_mock) { 22 | const mock_server = require("./src/api/mock-server.js"); 23 | mock_server(devServer.app); 24 | } 25 | return middlewares; 26 | }, 27 | }, 28 | 29 | chainWebpack: (config) => { 30 | config.plugin("provide").use(webpack.ProvidePlugin, [ 31 | { 32 | XE: "xe-utils", 33 | }, 34 | ]); 35 | config.plugin("define").use(webpack.DefinePlugin, [ 36 | { 37 | VE_ENV: { 38 | MODE: JSON.stringify(process.env.NODE_ENV), 39 | }, 40 | }, 41 | ]); 42 | config.plugins.delete("prefetch"); 43 | // config.plugins.delete("preload"); 44 | // config.optimization.delete("splitChunks"); 45 | }, 46 | 47 | configureWebpack: () => { 48 | let baseConfig = {}; 49 | let envConfig = {}; 50 | if (process.env.NODE_ENV === "production") { 51 | // 为生产环境修改配置... 52 | envConfig = { 53 | optimization: { 54 | splitChunks: { 55 | chunks: "all", 56 | // enforceSizeThreshold: 20000, 57 | cacheGroups: { 58 | echarts: { 59 | name: "chunk-echarts", 60 | priority: 20, 61 | test: /[\\/]node_modules[\\/]_?echarts(.*)/, 62 | }, 63 | elementPlus: { 64 | name: "chunk-elementPlus", 65 | priority: 20, 66 | test: /[\\/]node_modules[\\/]_?element-plus(.*)/, 67 | }, 68 | elementPlusIcon: { 69 | name: "chunk-elementPlusIcon", 70 | priority: 20, 71 | test: /[\\/]node_modules[\\/]_?@element-plus[\\/]icons(.*)/, 72 | }, 73 | mockjs: { 74 | name: "chunk-mockjs", 75 | priority: 20, 76 | test: /[\\/]node_modules[\\/]_?mockjs(.*)/, 77 | }, 78 | }, 79 | }, 80 | }, 81 | externals: { 82 | // lodash: "_" 83 | }, 84 | plugins: [ 85 | new TerserPlugin({ 86 | terserOptions: { 87 | compress: { 88 | drop_console: true, 89 | drop_debugger: true, 90 | }, 91 | }, 92 | }), 93 | new CompressionWebpackPlugin({ 94 | filename: "[path][base].gz", 95 | algorithm: "gzip", 96 | // test: /\.js$|\.html$|\.json$|\.css/, 97 | test: /\.js$|\.json$|\.css/, 98 | threshold: 10240, // 只有大小大于该值的资源会被处理 99 | minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理 100 | // deleteOriginalAssets: true // 删除原文件 101 | }), 102 | ], 103 | }; 104 | } 105 | return Object.assign(baseConfig, envConfig); 106 | }, 107 | 108 | css: { 109 | loaderOptions: { 110 | scss: { 111 | // 注意:在 sass-loader v8 中,这个选项名是 "prependData" 112 | // additionalData: `@import "~@/styles/imports.scss";` 113 | additionalData: Object.keys(scssVariables) 114 | .map((k) => `$${k.replace("_", "-")}: ${scssVariables[k]};`) 115 | .join("\n"), 116 | }, 117 | }, 118 | }, 119 | }; 120 | --------------------------------------------------------------------------------