├── .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 | 
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 | 
26 | 
27 | 
28 | 
29 | 
30 | 
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 |
10 |
11 |
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 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
⭐
17 |
⭐
18 |
⭐
19 |
⭐
20 |
⭐
21 |
22 |
23 |
404
24 |
🐱🐱🐱(⓿_⓿)🐱🐱🐱
25 |
看来你是迷路了......
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
335 |
336 |
509 |
--------------------------------------------------------------------------------
/src/components/FunctionPage.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/dashboard/LiveChart.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
17 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/components/dashboard/Shortcuts.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
45 |
46 |
47 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
123 |
--------------------------------------------------------------------------------
/src/components/layout/NavigateBar.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
21 |
28 |
--------------------------------------------------------------------------------
/src/components/layout/SideBar.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
32 |
33 |
34 |
60 |
61 |
70 |
--------------------------------------------------------------------------------
/src/components/layout/components/Breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | 首页
12 |
13 | {{ item }}
14 |
15 |
16 |
17 |
18 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/components/layout/components/Hamburger.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
17 |
27 |
28 |
40 |
--------------------------------------------------------------------------------
/src/components/layout/components/Logo.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
24 |
25 |
26 |
27 |
34 |
35 |
66 |
--------------------------------------------------------------------------------
/src/components/layout/components/Personal.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 你好!{{ uname }}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 退出登录
58 |
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
87 |
88 |
101 |
--------------------------------------------------------------------------------
/src/components/layout/components/SlideMenu.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
14 |
15 |
16 |
17 |
18 | {{ menu.name }}
19 |
20 |
25 |
26 |
31 |
32 |
33 |
34 |
35 | {{ menu.name }}
36 |
37 |
38 |
39 |
40 |
79 |
80 |
94 |
--------------------------------------------------------------------------------
/src/components/veBaseComponents/VeTable.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 | (ve_rowIndex = rowClick(event))
26 | "
27 | :row-class-name="
28 | ({ rowIndex }) => rowClassName(rowIndex, ve_rowIndex)
29 | "
30 | :cell-class-name="
31 | ({ rowIndex }) => cellClassName(rowIndex, ve_rowIndex)
32 | "
33 | header-row-class-name="ve_header_row_class_name"
34 | header-cell-class-name="ve_header_cell_class_name"
35 | style="width: 100%"
36 | v-bind="$attrs.table"
37 | >
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
57 |
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 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/AppMain.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
51 |
60 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
46 |
--------------------------------------------------------------------------------
/src/views/IFrame.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
44 |
45 |
51 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | vue3-element-admin
14 |
15 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
51 | 登录
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
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 |
9 |
83 |
84 |
85 |
92 |
93 |
196 |
197 |
263 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/Menus.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
25 | {{ menus.search.name }}
26 |
27 |
28 | 重置
29 |
30 |
31 |
32 |
33 |
34 |
51 |
52 |
58 | {{ menus.add.name }}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 | {{ row.icon }}
72 |
73 |
74 | /
75 |
76 |
77 |
78 |
79 |
88 | {{
89 | row.type == 0
90 | ? "目录"
91 | : row.type == 1
92 | ? "菜单"
93 | : "按钮"
94 | }}
95 |
96 |
97 |
98 |
99 |
100 |
101 | {{ row.sort }}
102 |
103 | /
104 |
105 |
106 |
107 |
108 |
113 | {{ row.iframe == 0 ? "否" : "是" }}
114 |
115 | /
116 |
117 |
118 |
119 |
120 |
121 |
127 | {{ row.url }}
128 |
129 | {{ row.url }}
130 |
131 | {{ row.menu }}
132 | /
133 |
134 |
135 |
136 |
137 |
143 | {{ menus.edit.name }}
144 |
145 |
151 | {{ menus.del.name }}
152 |
153 |
160 | {{ menus.addChild.name }}
161 |
162 |
169 | {{ menus.addBtn.name }}
170 |
171 |
172 |
173 |
174 |
175 |
176 |
184 |
185 |
186 |
201 |
202 |
301 |
302 |
303 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/Roles.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
25 | {{ menus.search.name }}
26 |
27 |
28 | 重置
29 |
30 |
31 |
32 |
33 |
34 |
48 |
49 |
55 | {{ menus.add.name }}
56 |
57 |
58 |
59 |
64 |
65 |
66 |
67 | {{ row.status == 0 ? "停用" : "启用" }}
68 |
69 |
70 |
71 |
72 |
73 |
79 | {{ menus.edit.name }}
80 |
81 |
87 | {{ menus.del.name }}
88 |
89 |
95 | {{ menus.member.name }}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
109 |
110 |
111 |
125 |
126 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/UserTable.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
28 | {{ menus.search.name }}
29 |
30 |
31 | 重置
32 |
33 |
34 |
35 |
36 |
50 |
51 |
58 | {{ menus.add.name }}
59 |
60 |
67 | {{ menus.add.name }}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
80 |
81 | {{
82 | row.password &&
83 | row.password
84 | .split("")
85 | .fill("*", 1, -1)
86 | .join()
87 | .replace(/\,/g, "")
88 | }}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | {{
97 | (row.role || row.role == 0) &&
98 | roleList.filter((item) => item.id == row.role)[0]
99 | .name
100 | }}
101 |
102 |
103 |
104 |
105 |
106 | handelSwitch(val, row)"
114 | >
115 | >
116 |
117 |
118 |
119 |
120 |
121 |
127 | {{ menus.edit.name }}
128 |
129 |
135 | {{ menus.del.name }}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
149 |
150 |
151 |
164 |
165 |
313 |
314 |
315 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/Users.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
28 | {{ menus.search.name }}
29 |
30 |
31 | 重置
32 |
33 |
34 |
35 |
36 |
37 |
51 |
52 |
59 | {{ menus.add.name }}
60 |
61 |
68 | {{ menus.add.name }}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
81 |
82 | {{
83 | row.password &&
84 | row.password
85 | .split("")
86 | .fill("*", 1, -1)
87 | .join()
88 | .replace(/\,/g, "")
89 | }}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | {{
98 | (row.role || row.role == 0) &&
99 | roleList.filter((item) => item.id == row.role)[0]
100 | .name
101 | }}
102 |
103 |
104 |
105 |
106 |
107 | handelSwitch(val, row)"
115 | >
116 | >
117 |
118 |
119 |
120 |
121 |
122 |
128 | {{ menus.edit.name }}
129 |
130 |
136 | {{ menus.del.name }}
137 |
138 |
139 |
140 |
141 |
142 |
143 |
150 |
151 |
152 |
165 |
166 |
315 |
316 |
317 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/components/MenuEdit.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
19 |
27 |
28 |
29 |
30 | 目录
31 |
32 |
33 | 菜单
34 |
35 |
36 | 按钮
37 |
38 |
39 |
40 |
41 |
58 |
59 |
60 |
61 |
67 |
68 |
69 |
70 |
71 |
72 |
81 |
82 |
87 |
88 |
89 |
90 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
113 |
114 | 否
115 | 是
116 |
117 |
118 |
119 |
125 |
132 |
139 | {{ item.url }}
140 |
141 | 描述 :{{ item.description }}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
155 | {{ item.name }}
156 |
157 |
158 |
159 | 该菜单下没有按钮
160 |
161 |
162 |
168 |
183 |
184 |
185 |
186 |
187 |
188 | 取消
189 | 确定
190 |
191 |
192 |
193 |
194 |
195 |
553 |
554 |
555 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/components/RoleEdit.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
41 |
42 |
52 |
53 |
57 | {{ data.name }}
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 启用
68 | 停用
69 |
70 |
71 |
72 |
73 |
74 |
75 | 取消
76 | 确定
77 |
78 |
79 |
80 |
81 |
82 |
247 |
248 |
263 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/components/UsersEdit.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
43 |
44 |
45 |
51 |
58 |
59 |
60 |
61 |
62 | 启用
63 | 停用
64 |
65 |
66 |
67 |
68 |
69 |
70 | 取消
71 | 确定
72 |
73 |
74 |
75 |
76 |
77 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/src/views/layoutpages/system/components/UsersEditRoute.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
47 |
54 |
55 |
56 |
57 |
58 | 启用
59 | 停用
60 |
61 |
62 |
63 | 取消
64 | 确定
65 |
66 |
67 |
68 |
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 |
--------------------------------------------------------------------------------