├── .env
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.html
├── mock
├── data
│ ├── admin.js
│ └── user.js
└── index.js
├── package.json
├── pay.png
├── public
└── favicon.ico
├── src
├── App.vue
├── api
│ ├── common.js
│ └── index.js
├── assets
│ └── images
│ │ ├── 401.png
│ │ ├── 404.png
│ │ ├── loginbg.png
│ │ └── tabs.png
├── components
│ └── crud
│ │ ├── brisk-dialogcom.vue
│ │ ├── brisk-operate.vue
│ │ ├── brisk-pagination.vue
│ │ ├── brisk-search-btn.vue
│ │ ├── brisk-toolbar.vue
│ │ └── index.js
├── lang
│ ├── en.js
│ ├── index.js
│ ├── modules
│ │ ├── admin
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── adminGroup
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── adminLog
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── adminRule
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── auth
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── crud
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── dashboard
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── error_page
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ ├── nested
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ │ └── profile
│ │ │ ├── index_en.js
│ │ │ └── index_zh.js
│ └── zh.js
├── main.js
├── router
│ └── index.js
├── settings
│ ├── settings.js
│ └── skin.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── app.js
│ │ ├── settings.js
│ │ └── user.js
├── styles
│ ├── color.scss
│ └── element
│ │ └── index.scss
├── utils
│ ├── http
│ │ ├── axios.js
│ │ └── index.js
│ ├── index.js
│ └── validate.js
├── vendor
│ └── Export2Excel.js
└── views
│ ├── 401
│ └── index.vue
│ ├── 404
│ └── index.vue
│ ├── admin
│ └── index.vue
│ ├── adminGroup
│ └── index.vue
│ ├── adminLog
│ └── index.vue
│ ├── adminRule
│ └── index.vue
│ ├── dashboard
│ └── index.vue
│ ├── layout
│ ├── components
│ │ ├── aside
│ │ │ ├── index.vue
│ │ │ ├── menu.vue
│ │ │ └── menuItem.vue
│ │ ├── header
│ │ │ └── index.vue
│ │ ├── setting
│ │ │ └── index.vue
│ │ └── tabs
│ │ │ └── index.vue
│ ├── index.vue
│ ├── mixin
│ │ └── ResizeHandler.js
│ └── nestedComponent.vue
│ ├── login
│ └── index.vue
│ ├── menu1
│ └── index.vue
│ ├── menu2
│ └── index.vue
│ ├── menu3
│ └── index.vue
│ ├── menu4
│ └── index.vue
│ └── profile
│ └── index.vue
└── vite.config.js
/.env:
--------------------------------------------------------------------------------
1 | VITE_APP_URL=http://127.0.0.1/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | package-lock.json
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 更新日志
2 | ## 1.4.0 (2021-12-25)
3 |
4 | - 1.升级element-plus至1.3.0-beta.5版本并更新所有依赖版本
5 | - 2.去除所有size单独配置
6 | - 3.优化header高度
7 | - 4.优化tabs栏样式
8 | - 5.更改resize事件弃用的触发标准
9 |
10 | ## 1.3.0 (2021-12-25)
11 |
12 | - 1.升级element-plus至1.2.0-beta.6版本并更新所有依赖版本
13 | - 2.vite.config.js相应升级变更
14 | - 3.新增系统主题色动态设置
15 | - 4.皮肤配置优化
16 | - 5.侧边菜单栏优化使其更美观
17 | - 6.公共组件优化调整
18 | - 7.element-plus废弃Font-Icon字体图标相应升级为element-plus/icons-vue
19 | - 8.element-plus自定义主题scss覆盖相应升级改造
20 | - 9.全局配置element-plus组件为small,去除之前每个页面的单独配置
21 | - 10.公共页面样式相应优化使其更美观
22 |
23 | ## 1.2.0 (2021-09-15)
24 |
25 | - 1.实现刷新当前路由
26 | - 2.皮肤配置调整加入logo背景色和文本颜色
27 | - 3.新增皮肤两个方案
28 | - 4.引入第三方icon库
29 | - 5.tabs标签渲染自定义icon图标
30 | - 6.菜单配置更改为第三方icon图标
31 | - 7.header内使用图标均更换为第三方icon库图标
32 |
33 | ## 1.1.0 (2021-09-14)
34 |
35 | - 1.新增tab标签栏
36 | - 2.路由配置新增tabShow以及keepAlive配置
37 | - 3.路由规则调整为首次进入时加载权限规则
38 | - 4.新增路由缓存以及去除缓存
39 |
40 | ## 1.0.1 (2021-09-03)
41 |
42 | - 皮肤配置新增选中颜色配置
43 | - 菜单选中颜色以及未选中颜色适配
44 | - store数据状态管理文件改为自动导入
45 | - lang语言包文件改为自动导入
46 |
47 | ## 1.0.0 (2021-08-23)
48 |
49 | - 第一个正式版发布
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 |
3 | [Brisk-Admin](https://github.com/ZHT131/brisk-admin) 是一个基于 [Vue3.0](https://github.com/vuejs/vue-next)、[Vite](https://github.com/vitejs/vite)、 [element-plus](https://element-plus.gitee.io/)、JavaScript的中后台解决方案,支持移动端响应式布局,它使用了最新的前端技术栈,并提炼了典型的业务页面,包括二次封装组件、动态路由菜单、国际化、动态换肤等功能,它可以帮助你快速搭建中后台项目,该项目使用最新的前端技术栈,使用javascript语法保留了对不熟悉typescript语法用户的友好,同时框架很适合轻松上手使用,希望给你带来帮助。
4 |
5 | ## 特性
6 | - **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
7 | - **皮肤**:可配置的主题
8 | - **Mock 数据** 内置 Mock 数据方案
9 | - **动态路由** 内置动态路由权限生成方案
10 | - **crud组件** 二次封装了crud常用组件方案
11 | - **国际化** 内置国际化方案
12 |
13 | ## 在线预览
14 | - [brisk-admin](http://brisk-admin.ybym.top/)
15 |
16 | 账号:admin或editor,密码:123456(随意)
17 |
18 | ## 文档
19 |
20 | [文档地址](http://brisk-admin-doc.ybym.top/)
21 |
22 | ## 准备
23 |
24 | - [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
25 | - [Vite](https://vitejs.dev/) - 熟悉 vite 特性
26 | - [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
27 | - [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
28 | - [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
29 | - [element-plus](https://element-plus.gitee.io/) - ui 基本使用
30 | - [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
31 |
32 | ## 安装使用
33 |
34 | - 获取项目代码
35 |
36 | ```bash
37 | git clone https://github.com/ZHT131/brisk-admin.git
38 | ```
39 |
40 | - 安装依赖
41 |
42 | ```bash
43 | cd brisk-admin
44 |
45 | npm install
46 |
47 | ```
48 |
49 | - 运行
50 |
51 | ```bash
52 | npm run dev
53 | ```
54 |
55 | - 打包
56 |
57 | ```bash
58 | npm run build
59 | ```
60 |
61 | ## 更新日志
62 |
63 | [CHANGELOG](./CHANGELOG.md)
64 |
65 | ## 如何贡献
66 |
67 | 非常欢迎你的加入 或者提交一个 Pull Request。
68 |
69 | **Pull Request:**
70 |
71 | 1. Fork 代码!
72 | 2. 创建自己的分支: `git checkout -b feat/xxxx`
73 | 3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
74 | 4. 推送您的分支: `git push origin feat/xxxx`
75 | 5. 提交`pull request`
76 |
77 | ## Git 贡献提交规范
78 |
79 | - 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
80 |
81 | - `feat` 增加新功能
82 | - `fix` 修复问题/BUG
83 | - `style` 代码风格相关无影响运行结果的
84 | - `perf` 优化/性能提升
85 | - `refactor` 重构
86 | - `revert` 撤销修改
87 | - `test` 测试相关
88 | - `docs` 文档/注释
89 | - `chore` 依赖更新/脚手架配置修改等
90 | - `workflow` 工作流改进
91 | - `ci` 持续集成
92 | - `types` 类型定义文件更改
93 | - `wip` 开发中
94 |
95 | ## 浏览器支持
96 |
97 | 本地开发推荐使用`Chrome 80+` 浏览器
98 |
99 | 支持现代浏览器, 不支持 IE
100 |
101 |
102 | ## 交流
103 |
104 | `Brisk Admin` 是完全开源免费的项目,在帮助开发者更方便地进行后台管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
105 |
106 | - QQ 群 [782498919](https://qm.qq.com/cgi-bin/qm/qr?k=88SWYIJNDUZaM_S2R4iN1uGOeh8ujqb0&jump_from=webapi)
107 |
108 | ## 赞助
109 | #### 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 🍹。
110 |
111 | 
112 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ymadmin
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/mock/data/admin.js:
--------------------------------------------------------------------------------
1 | import Mock from "mockjs";
2 |
3 | const adminUser = (params) => {
4 | let body = JSON.parse(params.body);
5 | let data = {
6 | page: body.page,
7 | size: body.size,
8 | rows: [],
9 | total: 30,
10 | };
11 |
12 | for (let index = 0; index < body.size; index++) {
13 | data.rows.push(
14 | Mock.mock({
15 | id: "@integer(1, 100)",
16 | username: "@string('lower', 5)",
17 | nickname: "@ctitle",
18 | group_id: "@integer(1, 10)",
19 | "group|2": {
20 | name: "管理员组",
21 | id: 1,
22 | },
23 | status: 1,
24 | status_text: "正常",
25 | createtime: "@datetime",
26 | })
27 | );
28 | }
29 | return data;
30 | };
31 |
32 | const adminGroup = (params) => {
33 | let body = JSON.parse(params.body);
34 | let data = {
35 | page: body.page,
36 | size: body.size,
37 | rows: [],
38 | total: 30,
39 | };
40 |
41 | for (let index = 0; index < body.size; index++) {
42 | data.rows.push(
43 | Mock.mock({
44 | id: "@integer(1, 100000)",
45 | pid: 0,
46 | name: "@ctitle",
47 | status: 1,
48 | status_text: "正常",
49 | createtime: "@datetime",
50 | children: [
51 | {
52 | id: "@integer(1, 100)",
53 | pid: 1,
54 | name: "@ctitle",
55 | status: 1,
56 | status_text: "正常",
57 | createtime: "@datetime",
58 | },
59 | ],
60 | })
61 | );
62 | }
63 | return data;
64 | };
65 |
66 | const adminLog = (params) => {
67 | let body = JSON.parse(params.body);
68 | let data = {
69 | page: body.page,
70 | size: body.size,
71 | rows: [],
72 | total: 30,
73 | };
74 |
75 | for (let index = 0; index < body.size; index++) {
76 | data.rows.push(
77 | Mock.mock({
78 | id: "@integer(1, 100)",
79 | username: "@string('lower', 5)",
80 | title: "@ctitle",
81 | ip: "127.0.0.1",
82 | path_url: "/admin/index/index",
83 | status: 1,
84 | status_text: "正常",
85 | createtime: "@datetime",
86 | })
87 | );
88 | }
89 | return data;
90 | };
91 |
92 | const adminRule = (params) => {
93 | let body = JSON.parse(params.body);
94 | let data = {
95 | page: body.page,
96 | size: body.size,
97 | rows: [],
98 | total: 30,
99 | };
100 |
101 | for (let index = 0; index < body.size; index++) {
102 | data.rows.push(
103 | Mock.mock({
104 | id: "@integer(1, 100)",
105 | pid: 0,
106 | title: "@ctitle",
107 | rule: "auth/admin",
108 | status: 1,
109 | status_text: "正常",
110 | createtime: "@datetime",
111 | children: [
112 | {
113 | id: "@integer(1, 100)",
114 | pid: 1,
115 | title: "@ctitle",
116 | rule: "auth/admin",
117 | status: 1,
118 | status_text: "正常",
119 | createtime: "@datetime",
120 | },
121 | ],
122 | })
123 | );
124 | }
125 | return data;
126 | };
127 |
128 | export { adminUser, adminGroup, adminLog, adminRule };
129 |
--------------------------------------------------------------------------------
/mock/data/user.js:
--------------------------------------------------------------------------------
1 | import Mock from "mockjs";
2 |
3 | const login = (params) => {
4 | const data = JSON.parse(params.body);
5 | const users = {
6 | admin: {
7 | token: "admin-token",
8 | avatar:
9 | "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
10 | nickname: "Admin",
11 | group: "admin",
12 | },
13 | editor: {
14 | token: "editor-token",
15 | avatar:
16 | "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
17 | nickname: "Editor",
18 | group: "editor",
19 | },
20 | };
21 | if (!users[data.username]) {
22 | return {
23 | code: 0,
24 | message: "Account and password are incorrect.",
25 | };
26 | }
27 |
28 | return {
29 | code: 1,
30 | data: users[data.username],
31 | };
32 | };
33 |
34 | const loginOut = (params) => {
35 | return {
36 | code: 1,
37 | data: params,
38 | };
39 | };
40 |
41 | const authRoutes = (params) => {
42 | let body = JSON.parse(params.body);
43 | if (body.group == "admin") {
44 | // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
45 | // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
46 | // 若你想不管路由下面的 children 声明的个数都显示你的根路由
47 | // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
48 | // 你可以设置 keepAlive: true,这样它就会缓存页面
49 | const routes = [
50 | {
51 | path: "/",
52 | component: "layout/index.vue",
53 | redirect: "/dashboard",
54 | meta: {
55 | title: "home",
56 | icon: "ri-home-line",
57 | keepAlive: false,
58 | tabShow: false,
59 | },
60 | alwaysShow: false,
61 | name: "app",
62 | children: [
63 | {
64 | path: "dashboard",
65 | component: "dashboard/index.vue",
66 | name: "dashboard",
67 | meta: {
68 | title: "dashboard",
69 | icon: "ri-home-line",
70 | keepAlive: true,
71 | tabShow: true,
72 | },
73 | redirect: null,
74 | alwaysShow: false,
75 | },
76 | ],
77 | },
78 | {
79 | path: "/profile",
80 | component: "layout/index.vue",
81 | redirect: "/profile/index",
82 | meta: {
83 | title: "profile",
84 | icon: "ri-home-line",
85 | keepAlive: false,
86 | tabShow: false,
87 | },
88 | name: "profile",
89 | alwaysShow: false,
90 | hidden: true,
91 | children: [
92 | {
93 | path: "index",
94 | component: "profile/index.vue",
95 | name: "profileIndex",
96 | meta: {
97 | title: "profileIndex",
98 | icon: "ri-home-line",
99 | keepAlive: true,
100 | tabShow: true,
101 | },
102 | redirect: null,
103 | alwaysShow: false,
104 | },
105 | ],
106 | },
107 | {
108 | path: "/auth",
109 | component: "layout/index.vue",
110 | redirect: null,
111 | meta: {
112 | title: "auth",
113 | icon: "ri-file-user-line",
114 | keepAlive: false,
115 | tabShow: false,
116 | },
117 | alwaysShow: true,
118 | name: "auth",
119 | children: [
120 | {
121 | path: "admin",
122 | component: "admin/index.vue",
123 | name: "admin",
124 | meta: {
125 | title: "admin",
126 | icon: "ri-admin-line",
127 | keepAlive: true,
128 | tabShow: true,
129 | },
130 | redirect: null,
131 | alwaysShow: false,
132 | },
133 | {
134 | path: "adminLog",
135 | component: "adminLog/index.vue",
136 | name: "adminLog",
137 | meta: {
138 | title: "adminLog",
139 | icon: "ri-file-list-line",
140 | keepAlive: true,
141 | tabShow: true,
142 | },
143 | alwaysShow: false,
144 | redirect: null,
145 | },
146 | {
147 | path: "adminGroup",
148 | component: "adminGroup/index.vue",
149 | name: "adminGroup",
150 | meta: {
151 | title: "adminGroup",
152 | icon: "ri-group-line",
153 | keepAlive: true,
154 | tabShow: true,
155 | },
156 | alwaysShow: false,
157 | redirect: null,
158 | },
159 | {
160 | path: "adminRule",
161 | component: "adminRule/index.vue",
162 | name: "adminRule",
163 | meta: {
164 | title: "adminRule",
165 | icon: "ri-menu-line",
166 | keepAlive: true,
167 | tabShow: true,
168 | },
169 | alwaysShow: false,
170 | redirect: null,
171 | },
172 | ],
173 | },
174 | {
175 | path: "/error_page",
176 | component: "layout/index.vue",
177 | name: "error_page",
178 | redirect: null,
179 | meta: {
180 | title: "error_page",
181 | icon: "ri-error-warning-line",
182 | keepAlive: false,
183 | tabShow: false,
184 | },
185 | alwaysShow: true,
186 | children: [
187 | {
188 | path: "401",
189 | component: "401/index.vue",
190 | name: "page401",
191 | meta: {
192 | title: "page401",
193 | icon: "ri-error-warning-line",
194 | keepAlive: true,
195 | tabShow: true,
196 | },
197 | redirect: null,
198 | alwaysShow: false,
199 | },
200 | {
201 | path: "404",
202 | component: "404/index.vue",
203 | name: "page404",
204 | meta: {
205 | title: "page404",
206 | icon: "ri-error-warning-line",
207 | keepAlive: true,
208 | tabShow: true,
209 | },
210 | redirect: null,
211 | alwaysShow: false,
212 | },
213 | ],
214 | },
215 | //嵌套路由示例
216 | {
217 | path: "/nested",
218 | component: "layout/index.vue",
219 | name: "nested",
220 | redirect: null,
221 | meta: {
222 | title: "nested",
223 | icon: "ri-stack-fill",
224 | keepAlive: false,
225 | tabShow: false,
226 | },
227 | alwaysShow: true,
228 | children: [
229 | {
230 | path: "menu",
231 | component: "noComponent",
232 | name: "menu",
233 | meta: {
234 | title: "menu",
235 | icon: "ri-apps-2-fill",
236 | keepAlive: false,
237 | tabShow: false,
238 | },
239 | redirect: null,
240 | alwaysShow: true,
241 | children: [
242 | {
243 | path: "menu2",
244 | component: "noComponent",
245 | name: "menu2",
246 | meta: {
247 | title: "menu2",
248 | icon: "ri-apps-2-fill",
249 | keepAlive: false,
250 | tabShow: false,
251 | },
252 | redirect: null,
253 | alwaysShow: true,
254 | children: [
255 | {
256 | path: "menu4",
257 | component: "menu4/index.vue",
258 | name: "menu4",
259 | meta: {
260 | title: "menu4",
261 | icon: "ri-apps-2-fill",
262 | keepAlive: true,
263 | tabShow: true,
264 | },
265 | redirect: null,
266 | alwaysShow: false,
267 | },
268 | ],
269 | },
270 | {
271 | path: "menu3",
272 | component: "menu3/index.vue",
273 | name: "menu3",
274 | meta: {
275 | title: "menu3",
276 | icon: "ri-apps-2-fill",
277 | keepAlive: true,
278 | tabShow: true,
279 | },
280 | redirect: null,
281 | alwaysShow: false,
282 | },
283 | ],
284 | },
285 | {
286 | path: "menu1",
287 | component: "menu1/index.vue",
288 | name: "menu1",
289 | meta: {
290 | title: "menu1",
291 | icon: "ri-apps-2-fill",
292 | keepAlive: true,
293 | tabShow: true,
294 | },
295 | redirect: null,
296 | alwaysShow: false,
297 | },
298 | ],
299 | },
300 | ];
301 | return {
302 | code: 1,
303 | data: routes,
304 | };
305 | } else {
306 | // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
307 | // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
308 | // 若你想不管路由下面的 children 声明的个数都显示你的根路由
309 | // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
310 | // 你可以设置 keepAlive: true,这样它就会缓存页面
311 | const routes = [
312 | {
313 | path: "/",
314 | component: "layout/index.vue",
315 | redirect: "/dashboard",
316 | meta: {
317 | title: "home",
318 | icon: "ri-home-line",
319 | keepAlive: false,
320 | tabShow: false,
321 | },
322 | alwaysShow: false,
323 | name: "app",
324 | children: [
325 | {
326 | path: "dashboard",
327 | component: "dashboard/index.vue",
328 | name: "dashboard",
329 | meta: {
330 | title: "dashboard",
331 | icon: "ri-home-line",
332 | keepAlive: true,
333 | tabShow: true,
334 | },
335 | redirect: null,
336 | alwaysShow: false,
337 | },
338 | ],
339 | },
340 | {
341 | path: "/profile",
342 | component: "layout/index.vue",
343 | redirect: "/profile/index",
344 | meta: {
345 | title: "profile",
346 | icon: "ri-home-line",
347 | keepAlive: false,
348 | tabShow: false,
349 | },
350 | name: "profile",
351 | alwaysShow: false,
352 | hidden: true,
353 | children: [
354 | {
355 | path: "index",
356 | component: "profile/index.vue",
357 | name: "profileIndex",
358 | meta: {
359 | title: "profileIndex",
360 | icon: "ri-home-line",
361 | keepAlive: true,
362 | tabShow: true,
363 | },
364 | redirect: null,
365 | alwaysShow: false,
366 | },
367 | ],
368 | },
369 | {
370 | path: "/auth",
371 | component: "layout/index.vue",
372 | redirect: null,
373 | meta: {
374 | title: "auth",
375 | icon: "ri-file-user-line",
376 | keepAlive: false,
377 | tabShow: false,
378 | },
379 | alwaysShow: true,
380 | name: "auth",
381 | children: [
382 | {
383 | path: "admin",
384 | component: "admin/index.vue",
385 | name: "admin",
386 | meta: {
387 | title: "admin",
388 | icon: "ri-admin-line",
389 | keepAlive: true,
390 | tabShow: true,
391 | },
392 | redirect: null,
393 | alwaysShow: false,
394 | },
395 | {
396 | path: "adminLog",
397 | component: "adminLog/index.vue",
398 | name: "adminLog",
399 | meta: {
400 | title: "adminLog",
401 | icon: "ri-file-list-line",
402 | keepAlive: true,
403 | tabShow: true,
404 | },
405 | alwaysShow: false,
406 | redirect: null,
407 | },
408 | ],
409 | },
410 | {
411 | path: "/error_page",
412 | component: "layout/index.vue",
413 | name: "error_page",
414 | redirect: null,
415 | meta: {
416 | title: "error_page",
417 | icon: "ri-error-warning-line",
418 | keepAlive: false,
419 | tabShow: false,
420 | },
421 | alwaysShow: true,
422 | children: [
423 | {
424 | path: "401",
425 | component: "401/index.vue",
426 | name: "page401",
427 | meta: {
428 | title: "page401",
429 | icon: "ri-error-warning-line",
430 | keepAlive: true,
431 | tabShow: true,
432 | },
433 | redirect: null,
434 | alwaysShow: false,
435 | },
436 | {
437 | path: "404",
438 | component: "404/index.vue",
439 | name: "page404",
440 | meta: {
441 | title: "page404",
442 | icon: "ri-error-warning-line",
443 | keepAlive: true,
444 | tabShow: true,
445 | },
446 | redirect: null,
447 | alwaysShow: false,
448 | },
449 | ],
450 | },
451 | //嵌套路由示例
452 | {
453 | path: "/nested",
454 | component: "layout/index.vue",
455 | name: "nested",
456 | redirect: null,
457 | meta: {
458 | title: "nested",
459 | icon: "ri-stack-fill",
460 | keepAlive: false,
461 | tabShow: false,
462 | },
463 | alwaysShow: true,
464 | children: [
465 | {
466 | path: "menu",
467 | component: "noComponent",
468 | name: "menu",
469 | meta: {
470 | title: "menu",
471 | icon: "ri-apps-2-fill",
472 | keepAlive: false,
473 | tabShow: false,
474 | },
475 | redirect: null,
476 | alwaysShow: true,
477 | children: [
478 | {
479 | path: "menu2",
480 | component: "noComponent",
481 | name: "menu2",
482 | meta: {
483 | title: "menu2",
484 | icon: "ri-apps-2-fill",
485 | keepAlive: false,
486 | tabShow: false,
487 | },
488 | redirect: null,
489 | alwaysShow: true,
490 | children: [
491 | {
492 | path: "menu4",
493 | component: "menu4/index.vue",
494 | name: "menu4",
495 | meta: {
496 | title: "menu4",
497 | icon: "ri-apps-2-fill",
498 | keepAlive: true,
499 | tabShow: true,
500 | },
501 | redirect: null,
502 | alwaysShow: false,
503 | },
504 | ],
505 | },
506 | {
507 | path: "menu3",
508 | component: "menu3/index.vue",
509 | name: "menu3",
510 | meta: {
511 | title: "menu3",
512 | icon: "ri-apps-2-fill",
513 | keepAlive: true,
514 | tabShow: true,
515 | },
516 | redirect: null,
517 | alwaysShow: false,
518 | },
519 | ],
520 | },
521 | {
522 | path: "menu1",
523 | component: "menu1/index.vue",
524 | name: "menu1",
525 | meta: {
526 | title: "menu1",
527 | icon: "ri-apps-2-fill",
528 | keepAlive: true,
529 | tabShow: true,
530 | },
531 | redirect: null,
532 | alwaysShow: false,
533 | },
534 | ],
535 | },
536 | ];
537 | return {
538 | code: 1,
539 | data: routes,
540 | };
541 | }
542 | };
543 |
544 | export { login, loginOut, authRoutes };
545 |
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | import Mock from "mockjs";
2 | import { adminUser, adminGroup, adminLog, adminRule } from "./data/admin";
3 | import { login, loginOut, authRoutes } from "./data/user";
4 | Mock.mock("http://127.0.0.1/api/login", "post", login);
5 | Mock.mock("http://127.0.0.1/api/authRoutes", "post", authRoutes);
6 | Mock.mock("http://127.0.0.1/api/adminUser", "post", adminUser);
7 | Mock.mock("http://127.0.0.1/api/adminGroup", "post", adminGroup);
8 | Mock.mock("http://127.0.0.1/api/adminLog", "post", adminLog);
9 | Mock.mock("http://127.0.0.1/api/adminRule", "post", adminRule);
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin-web",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "@antv/g2": "^4.1.37",
11 | "@element-plus/icons-vue": "^0.2.4",
12 | "axios": "^0.24.0",
13 | "element-plus": "^1.3.0-beta.5",
14 | "file-saver": "^2.0.5",
15 | "js-cookie": "^3.0.1",
16 | "mockjs": "^1.1.0",
17 | "nprogress": "^0.2.0",
18 | "remixicon": "^2.5.0",
19 | "screenfull": "^6.0.0",
20 | "vue": "^3.2.25",
21 | "vue-i18n": "^9.1.7",
22 | "vue-router": "^4.0.11",
23 | "vuex": "^4.0.2",
24 | "xlsx": "^0.17.5"
25 | },
26 | "devDependencies": {
27 | "@vitejs/plugin-vue": "^2.0.1",
28 | "unplugin-element-plus": "^0.2.0",
29 | "sass": "^1.47.0",
30 | "vite": "^2.7.10"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/pay.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
34 |
--------------------------------------------------------------------------------
/src/api/common.js:
--------------------------------------------------------------------------------
1 | import axios from "../utils/http/axios";
2 | //公共请求
3 | export function comReq(url, method, data) {
4 | return axios({
5 | url: url,
6 | method: method,
7 | data,
8 | config: {},
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from "../utils/http/axios"
2 |
3 | export const login = (data) => {
4 | return axios({
5 | url: "api/login",
6 | method: "post",
7 | data,
8 | config: {}
9 | })
10 | }
11 | export const authRoutes = (data) => {
12 | return axios({
13 | url: "api/authRoutes",
14 | method: "post",
15 | data,
16 | config: {}
17 | })
18 | }
--------------------------------------------------------------------------------
/src/assets/images/401.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/src/assets/images/401.png
--------------------------------------------------------------------------------
/src/assets/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/src/assets/images/404.png
--------------------------------------------------------------------------------
/src/assets/images/loginbg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/src/assets/images/loginbg.png
--------------------------------------------------------------------------------
/src/assets/images/tabs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZHT131/brisk-admin/4b40d75d3fad7503fbe3160acdd3a055de35d97b/src/assets/images/tabs.png
--------------------------------------------------------------------------------
/src/components/crud/brisk-dialogcom.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
15 |
16 |
77 |
82 |
--------------------------------------------------------------------------------
/src/components/crud/brisk-operate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/crud/brisk-pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
17 |
18 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/crud/brisk-search-btn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{$t('searchBtn.query')}}
6 | {{$t('searchBtn.reset')}}
7 |
8 |
9 |
10 |
16 |
24 |
--------------------------------------------------------------------------------
/src/components/crud/brisk-toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{$t("toolBar.add")}}
8 |
9 |
10 | {{$t("toolBar.edit")}}
11 |
12 |
13 | {{$t("toolBar.delete")}}
14 |
15 |
16 |
17 |
18 | {{$t("toolBar.export")}}
19 |
20 |
21 |
22 |
xlsx
23 |
csv
24 |
txt
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {{$t("toolBar.select_all")}}
40 |
41 |
42 | {{ $t(item.label) }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
68 |
69 |
86 |
--------------------------------------------------------------------------------
/src/components/crud/index.js:
--------------------------------------------------------------------------------
1 | import { comReq } from "~/api/common.js";
2 | import { ElMessage } from "element-plus";
3 | function curd(options) {
4 | return {
5 | data() {
6 | return {
7 | //显示/隐藏搜索栏
8 | showSearch: false,
9 | // 主页操作栏显示哪些按钮
10 | toolShow: {
11 | add: true,
12 | edit: true,
13 | del: true,
14 | export: true,
15 | },
16 | //显示/隐藏列数据
17 | showColumns: {},
18 | //表格加载状态
19 | loadingStatus: false,
20 | //表格列筛选数据
21 | tableColumns: [],
22 | //表格列选中状态
23 | allColumnsSelected: true,
24 | allColumnsSelectedIndeterminate: false,
25 | //分页组件属性
26 | pageMobileLayout: "total, prev, pager, next",
27 | pageDesktopLayout: "total, sizes, prev, pager, next, jumper",
28 | currentPage: 1,
29 | pageTotal: 0,
30 | pageSize: 10,
31 | pageSizes: [10, 20, 30, 40, 50],
32 | //表格数据
33 | tableData: [],
34 | //请求url
35 | url: "",
36 | //操作栏显示按钮
37 | showOperate: {
38 | view: true,
39 | edit: true,
40 | del: true,
41 | },
42 | addDialogFormVisible: false,
43 | editDialogFormVisible: false,
44 | detailDialogFormVisible: false,
45 | multipleSelection: [],
46 | exportLoading: false, //导出按钮动画
47 | filename: "", //导出文件名
48 | autoWidth: true, //导出自动宽度
49 | bookType: "xlsx", //导出文件类型
50 | //导入并合并初始化数据
51 | ...options,
52 | };
53 | },
54 | created() {
55 | // 加载数据
56 | this.getList();
57 | },
58 | methods: {
59 | // 请求查询列表数据
60 | getList() {
61 | this.loadingStatus = true;
62 | comReq(this.url, "post", {
63 | page: this.currentPage,
64 | size: this.pageSize,
65 | })
66 | .then((res) => {
67 | this.loadingStatus = false;
68 | this.tableData = res.rows;
69 | this.pageTotal = res.total;
70 | })
71 | .catch(() => {
72 | this.loadStatus = false;
73 | });
74 | },
75 | // 全选列
76 | handleCheckAllChange(val) {
77 | if (val === false) {
78 | this.allColumnsSelected = true;
79 | return;
80 | }
81 | this.tableColumns.forEach((column) => {
82 | if (!column.visible) {
83 | column.visible = true;
84 | this.updateColumnVisible(column);
85 | }
86 | });
87 | this.allColumnsSelected = val;
88 | this.allColumnsSelectedIndeterminate = false;
89 | },
90 | // 单选列
91 | handleCheckChange(item) {
92 | let totalCount = 0;
93 | let selectedCount = 0;
94 | this.tableColumns.forEach((column) => {
95 | ++totalCount;
96 | selectedCount += column.visible ? 1 : 0;
97 | });
98 | if (selectedCount === 0) {
99 | console.log("至少选择一项");
100 | this.$nextTick(function () {
101 | item.visible = true;
102 | });
103 | return;
104 | }
105 | this.allColumnsSelected = selectedCount === totalCount;
106 | this.allColumnsSelectedIndeterminate =
107 | selectedCount !== totalCount && selectedCount !== 0;
108 | this.updateColumnVisible(item);
109 | },
110 | //更新列
111 | updateColumnVisible(item) {
112 | this.showColumns[item.property] = item.visible;
113 | },
114 | // 显示/隐藏搜索
115 | changeSearchShow() {
116 | this.showSearch = !this.showSearch;
117 | },
118 | // 刷新表格数据
119 | refresh() {
120 | this.getList();
121 | },
122 | //提交搜索
123 | submitSearchForm() {
124 | this.$refs.searchForm.validate((valid) => {
125 | if (valid) {
126 | console.log("submit!");
127 | } else {
128 | console.log("error submit!!");
129 | return false;
130 | }
131 | });
132 | },
133 | //重置搜索
134 | resetSearchForm() {
135 | this.$refs.searchForm.resetFields();
136 | },
137 | //每页条数变化
138 | handleSizeChange(val) {
139 | this.pageSize = val;
140 | this.currentPage = 1;
141 | this.getList();
142 | },
143 | //页码变化
144 | handleCurrentChange(val) {
145 | this.currentPage = val;
146 | this.getList();
147 | },
148 | //表格选择变化
149 | selectionChange(val) {
150 | this.multipleSelection = val;
151 | },
152 | //新增
153 | handleAdd() {
154 | this.addDialogFormVisible = true;
155 | },
156 | //选择编辑
157 | handleSelectEdit() {
158 | if (this.multipleSelection.length === 0) {
159 | return ElMessage("请先选择需要编辑的数据");
160 | }
161 | this.editForm = this.multipleSelection[0];
162 | this.editDialogFormVisible = true;
163 | },
164 | //选择删除
165 | handleSelectDel() {
166 | if (this.multipleSelection.length === 0) {
167 | return ElMessage("请先选择需要删除的数据");
168 | }
169 | this.$confirm(
170 | this.$t("delConfirm.delmsg"),
171 | this.$t("delConfirm.title"),
172 | {
173 | confirmButtonText: this.$t("delConfirm.comfirm"),
174 | cancelButtonText: this.$t("delConfirm.cancle"),
175 | type: "warning",
176 | }
177 | )
178 | .then(() => {
179 | this.$message({
180 | type: "success",
181 | message: "删除成功!",
182 | });
183 | })
184 | .catch(() => {
185 | this.$message({
186 | type: "info",
187 | message: "已取消删除",
188 | });
189 | });
190 | },
191 | //导出
192 | handleExport(type) {
193 | this.bookType = type;
194 | this.exportLoading = true;
195 | let tHeader = [];
196 | let filterVal = [];
197 | this.tableColumns.map((item) => {
198 | tHeader.push(this.$t(item.label));
199 | filterVal.push(item.property);
200 | });
201 | import("~/vendor/Export2Excel").then((excel) => {
202 | const list = this.tableData;
203 | const data = this.formatJson(filterVal, list);
204 | excel.export_json_to_excel({
205 | header: tHeader,
206 | data,
207 | filename: this.filename,
208 | autoWidth: this.autoWidth,
209 | bookType: this.bookType,
210 | });
211 | this.exportLoading = false;
212 | });
213 | },
214 | //处理并获取导出数据
215 | formatJson(filterVal, jsonData) {
216 | return jsonData.map((v) =>
217 | filterVal.map((j) => {
218 | if (j.indexOf("_dot_") != -1) {
219 | let arr = j.split("_dot_");
220 | let val = this.exportTreeFilter(v, arr);
221 | return val;
222 | } else {
223 | return v[j];
224 | }
225 | })
226 | );
227 | },
228 | //递归处理关联嵌套数据
229 | exportTreeFilter(v, arr) {
230 | if (arr.length > 0) {
231 | let newarr = arr;
232 | for (let index = 0; index < arr.length; index++) {
233 | const item = arr[index];
234 | newarr.splice(index, 1);
235 | return this.exportTreeFilter(v[item], newarr);
236 | }
237 | } else {
238 | return v;
239 | }
240 | },
241 | //查看
242 | handleView(row) {
243 | this.detailDialogFormVisible = true;
244 | },
245 | //编辑
246 | handleEdit(row) {
247 | this.editForm = row;
248 | this.editDialogFormVisible = true;
249 | },
250 | //删除
251 | handleDel(row) {
252 | console.log(row);
253 | this.$confirm(
254 | this.$t("delConfirm.delmsg"),
255 | this.$t("delConfirm.title"),
256 | {
257 | confirmButtonText: this.$t("delConfirm.comfirm"),
258 | cancelButtonText: this.$t("delConfirm.cancle"),
259 | type: "warning",
260 | }
261 | )
262 | .then(() => {
263 | this.$message({
264 | type: "success",
265 | message: "删除成功!",
266 | });
267 | })
268 | .catch(() => {
269 | this.$message({
270 | type: "info",
271 | message: "已取消删除",
272 | });
273 | });
274 | },
275 | //提交新增
276 | addSubmit() {
277 | console.log(this.addForm);
278 | ElMessage.success({
279 | message: "演示执行新增提交",
280 | type: "success",
281 | });
282 | this.addDialogFormVisible = false;
283 | },
284 | //取消新增
285 | addCancle() {
286 | this.$refs.addForm.resetFields();
287 | this.addDialogFormVisible = false;
288 | },
289 | //提交编辑
290 | editSubmit() {
291 | console.log(this.addForm);
292 | ElMessage.success({
293 | message: "演示执行编辑提交",
294 | type: "success",
295 | });
296 | this.editDialogFormVisible = false;
297 | },
298 | //取消编辑
299 | editCancle() {
300 | this.$refs.editForm.resetFields();
301 | this.editDialogFormVisible = false;
302 | },
303 | //详情确认
304 | detailCancle() {
305 | this.detailDialogFormVisible = false;
306 | },
307 | //详情取消
308 | detailSubmit() {
309 | this.detailDialogFormVisible = false;
310 | },
311 | },
312 | };
313 | }
314 |
315 | export default curd;
316 |
317 | // 分页mixins
318 | var pagination = {
319 | props: {
320 | currentPage: {
321 | type: Number,
322 | default: 1,
323 | },
324 | pageSize: {
325 | type: Number,
326 | default: 10,
327 | },
328 | pageSizes: {
329 | type: Array,
330 | default: [],
331 | },
332 | pageTotal: {
333 | type: Number,
334 | default: 0,
335 | },
336 | pageMobileLayout: {
337 | type: String,
338 | default: "",
339 | },
340 | pageDesktopLayout: {
341 | type: String,
342 | default: "",
343 | },
344 | device: {
345 | type: String,
346 | default: "",
347 | },
348 | },
349 | methods: {
350 | //每页条数变化
351 | handleSizeChange(val) {
352 | this.$emit("handleSizeChange", val);
353 | },
354 | //当前页码变化
355 | handleCurrentChange(val) {
356 | this.$emit("handleCurrentChange", val);
357 | },
358 | },
359 | };
360 | // 搜索mixins
361 | var search = {
362 | props: {},
363 | methods: {
364 | // 提交搜索
365 | submitSearchForm(val) {
366 | this.$emit("submitSearchForm", val);
367 | },
368 | // 重置搜索
369 | resetSearchForm(item) {
370 | this.$emit("resetSearchForm", item);
371 | },
372 | },
373 | };
374 | // 工具栏mixins
375 | var tools = {
376 | props: {
377 | toolShow: {
378 | type: Object,
379 | default: {},
380 | },
381 | tableColumns: {
382 | type: Array,
383 | default: [],
384 | },
385 | allColumnsSelected: {
386 | type: Boolean,
387 | default: true,
388 | },
389 | allColumnsSelectedIndeterminate: {
390 | type: Boolean,
391 | default: false,
392 | },
393 | exportLoading: {
394 | type: Boolean,
395 | default: false,
396 | },
397 | },
398 | methods: {
399 | // 全选列
400 | checkAllChange(val) {
401 | this.$emit("handleCheckAllChange", val);
402 | },
403 | // 单选列
404 | checkChange(item) {
405 | this.$emit("handleCheckChange", item);
406 | },
407 | changeSearchShow() {
408 | this.$emit("changeSearchShow");
409 | },
410 | refresh() {
411 | this.$emit("refresh");
412 | },
413 | },
414 | };
415 |
416 | // 操作栏mixins
417 | var operate = {
418 | props: {
419 | width: {
420 | type: Number,
421 | default: 180,
422 | },
423 | device: {
424 | type: String,
425 | default: "",
426 | },
427 | showOperate: {
428 | type: Object,
429 | default: {},
430 | },
431 | },
432 | methods: {
433 | handleView(row) {
434 | this.$emit("handleView", row);
435 | },
436 | handleEdit(row) {
437 | this.$emit("handleEdit", row);
438 | },
439 | handleDel(row) {
440 | this.$emit("handleDel", row);
441 | },
442 | },
443 | };
444 |
445 | export { pagination, search, tools, operate };
446 |
--------------------------------------------------------------------------------
/src/lang/en.js:
--------------------------------------------------------------------------------
1 | //en
2 | const modulesFiles = import.meta.globEager("./modules/*/index_en.js");
3 | const modules = Object.keys(modulesFiles).reduce((modules, modulePath) => {
4 | const value = modulesFiles[modulePath];
5 | Object.assign(modules, value.default);
6 | return modules;
7 | }, {});
8 |
9 | export default {
10 | app: {
11 | home: "home",
12 | setting_title: "setting",
13 | },
14 | userDropdown: {
15 | userinfo: "userinfo",
16 | loginout: "loginout",
17 | },
18 | login: {
19 | login: "login",
20 | username: "username",
21 | password: "password",
22 | usernamePlaceholder: "please enter user name",
23 | passwordPlaceholder: "Please enter the password",
24 | loginBtn: "login",
25 | },
26 | 401: {
27 | 401: "401",
28 | },
29 | 404: {
30 | 404: "404",
31 | },
32 | ...modules,
33 | };
34 |
--------------------------------------------------------------------------------
/src/lang/index.js:
--------------------------------------------------------------------------------
1 | import { createI18n } from "vue-i18n";
2 | import enLocale from "element-plus/lib/locale/lang/en";
3 | import zhLocale from "element-plus/lib/locale/lang/zh-cn";
4 | import enLang from "./en";
5 | import zhLang from "./zh";
6 | import Cookies from "js-cookie";
7 |
8 | const messages = {
9 | [enLocale.name]: {
10 | // el 这个属性很关键,一定要保证有这个属性,
11 | el: enLocale.el,
12 | // 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
13 | ...enLang,
14 | },
15 | [zhLocale.name]: {
16 | el: zhLocale.el,
17 | // 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
18 | ...zhLang,
19 | },
20 | };
21 |
22 | const i18n = createI18n({
23 | locale: Cookies.get("language") == "en" ? enLocale.name : zhLocale.name,//加入判断修复刷新页面不渲染选中语言的问题
24 | fallbackLocale: enLocale.name,
25 | messages,
26 | });
27 |
28 | export default i18n;
29 |
--------------------------------------------------------------------------------
/src/lang/modules/admin/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | admin: {
4 | admin: "admin",
5 | field: {
6 | id: "id",
7 | username: "username",
8 | nickname: "nickname",
9 | group_id: "group_id",
10 | group: {
11 | name: "group_name",
12 | },
13 | status: "status",
14 | status_1: "status_1",
15 | status_2: "status_2",
16 | },
17 | component: {
18 | select_placeholder: "choose",
19 | addlog_add_title: "add",
20 | addlog_edit_title: "edit",
21 | addlog_detail_title: "detail",
22 | detail_title: "title",
23 | detail_content: "content",
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/lang/modules/admin/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | admin: {
4 | admin: "管理员管理",
5 | field: {
6 | id: "ID",
7 | username: "用户名",
8 | nickname: "昵称",
9 | group_id: "组别ID",
10 | group: {
11 | name: "角色组名",
12 | },
13 | status: "状态",
14 | status_1: "正常",
15 | status_2: "禁用",
16 | },
17 | component: {
18 | select_placeholder: "请选择",
19 | addlog_add_title: "新增",
20 | addlog_edit_title: "编辑",
21 | addlog_detail_title: "详情",
22 | detail_title:"标题",
23 | detail_content:"内容",
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/lang/modules/adminGroup/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | adminGroup: {
4 | adminGroup: "adminGroup",
5 | field: {
6 | id: "id",
7 | name: "name",
8 | pid: "pid",
9 | status: "status",
10 | status_1: "status_1",
11 | status_2: "status_2",
12 | },
13 | component: {
14 | select_placeholder: "choose",
15 | addlog_add_title: "add",
16 | addlog_edit_title: "edit",
17 | addlog_detail_title: "detail",
18 | detail_title: "title",
19 | detail_content: "content",
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/lang/modules/adminGroup/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | adminGroup: {
4 | adminGroup: "管理员角色组",
5 | field: {
6 | id: "ID",
7 | name: "角色名",
8 | pid: "父ID",
9 | status: "状态",
10 | status_1: "正常",
11 | status_2: "禁用",
12 | },
13 | component: {
14 | select_placeholder: "请选择",
15 | addlog_add_title: "新增",
16 | addlog_edit_title: "编辑",
17 | addlog_detail_title: "详情",
18 | detail_title: "标题",
19 | detail_content: "内容",
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/lang/modules/adminLog/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | adminLog: {
4 | adminLog: "adminLog",
5 | field: {
6 | id: "id",
7 | username: "username",
8 | title: "title",
9 | path_url: "path_url",
10 | ip:'IP',
11 | status: "status",
12 | status_1: "status_1",
13 | status_2: "status_2",
14 | },
15 | component: {
16 | select_placeholder: "choose",
17 | addlog_add_title: "add",
18 | addlog_edit_title: "edit",
19 | addlog_detail_title: "detail",
20 | detail_title: "title",
21 | detail_content: "content",
22 | },
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/src/lang/modules/adminLog/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | adminLog: {
4 | adminLog: "管理员日志",
5 | field: {
6 | id: "ID",
7 | username: "用户名",
8 | title: "标题",
9 | path_url: "操作Url",
10 | ip:'IP',
11 | status: "状态",
12 | status_1: "正常",
13 | status_2: "禁用",
14 | },
15 | component: {
16 | select_placeholder: "请选择",
17 | addlog_add_title: "新增",
18 | addlog_edit_title: "编辑",
19 | addlog_detail_title: "详情",
20 | detail_title: "标题",
21 | detail_content: "内容",
22 | },
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/src/lang/modules/adminRule/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | adminRule: {
4 | adminRule: "adminRule",
5 | field: {
6 | id: "id",
7 | title: "title",
8 | rule: "rule",
9 | status: "status",
10 | status_1: "status_1",
11 | status_2: "status_2",
12 | },
13 | component: {
14 | select_placeholder: "choose",
15 | addlog_add_title: "add",
16 | addlog_edit_title: "edit",
17 | addlog_detail_title: "detail",
18 | detail_title: "title",
19 | detail_content: "content",
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/lang/modules/adminRule/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | adminRule: {
4 | adminRule: "菜单规则",
5 | field: {
6 | id: "ID",
7 | title: "标题",
8 | rule: "规则",
9 | status: "状态",
10 | status_1: "正常",
11 | status_2: "禁用",
12 | },
13 | component: {
14 | select_placeholder: "请选择",
15 | addlog_add_title: "新增",
16 | addlog_edit_title: "编辑",
17 | addlog_detail_title: "详情",
18 | detail_title: "标题",
19 | detail_content: "内容",
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/src/lang/modules/auth/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | auth:{
4 | auth:'auth',
5 | }
6 | }
--------------------------------------------------------------------------------
/src/lang/modules/auth/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | auth:{
4 | auth:'权限管理',
5 | }
6 | }
--------------------------------------------------------------------------------
/src/lang/modules/crud/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | searchBtn: {
4 | query: "query",
5 | reset: "reset",
6 | },
7 | toolBar: {
8 | add: "add",
9 | edit: "edit",
10 | delete: "delet",
11 | export: "export",
12 | select_all: "select_all",
13 | },
14 | table: {
15 | operate: "operate",
16 | },
17 | dialogcom: {
18 | comfirm: "comfirm",
19 | cancle: "cancle",
20 | },
21 | delConfirm: {
22 | delmsg:
23 | "This operation will permanently delete the file, do you want to continue?",
24 | title: "hint",
25 | comfirm: "comfirm",
26 | cancle: "cancle",
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/src/lang/modules/crud/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | searchBtn: {
4 | query: "查询",
5 | reset: "重置",
6 | },
7 | toolBar: {
8 | add: "新增",
9 | edit: "编辑",
10 | delete: "删除",
11 | export: "导出",
12 | select_all: "全选",
13 | },
14 | table: {
15 | operate: "操作",
16 | },
17 | dialogcom: {
18 | comfirm: "确定",
19 | cancle: "取消",
20 | },
21 | delConfirm: {
22 | delmsg: "此操作将永久删除该文件, 是否继续?",
23 | title: "提示",
24 | comfirm: "确定",
25 | cancle: "取消",
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/src/lang/modules/dashboard/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | dashboard:{
4 | dashboard:'dashboard',
5 | totalUserNumber:'Total number of users',
6 | totalVisits:'Total visits',
7 | totalOrderNumber:'Total number of orders',
8 | totalSalesAmount:'Total sales amount',
9 | sales: 'Sales(yuan)',
10 | orderNumber: 'Number of order(single)',
11 | wxPay: 'WeChat Pay',
12 | aliPay: 'Pay with Ali-Pay',
13 | walletPay: 'Wallet payment',
14 | otherPay: 'Other payment',
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/lang/modules/dashboard/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | dashboard: {
4 | dashboard: '控制台',
5 | totalUserNumber: '总用户数',
6 | totalVisits: '总访问量',
7 | totalOrderNumber: '总订单数',
8 | totalSalesAmount: '总销售金额',
9 | sales: '销售额(元)',
10 | orderNumber: '订单数(单)',
11 | wxPay: '微信支付',
12 | aliPay: '支付宝支付',
13 | walletPay: '钱包支付',
14 | otherPay: '其他支付',
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/lang/modules/error_page/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | error_page:{
4 | error_page:'error_page',
5 | },
6 | page401:{
7 | page401:'401'
8 | },
9 | page404:{
10 | page404:'404'
11 | }
12 | }
--------------------------------------------------------------------------------
/src/lang/modules/error_page/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | error_page:{
4 | error_page:'错误页面',
5 | },
6 | page401:{
7 | page401:'401'
8 | },
9 | page404:{
10 | page404:'404'
11 | }
12 | }
--------------------------------------------------------------------------------
/src/lang/modules/nested/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | nested:{
4 | nested:'nested'
5 | },
6 | menu:{
7 | menu:'menu',
8 | },
9 | menu1:{
10 | menu1:'menu1',
11 | },
12 | menu2:{
13 | menu2:'menu2'
14 | },
15 | menu3:{
16 | menu3:'menu3'
17 | },
18 | menu4:{
19 | menu4:'menu4'
20 | }
21 | }
--------------------------------------------------------------------------------
/src/lang/modules/nested/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | nested:{
4 | nested:'嵌套路由'
5 | },
6 | menu:{
7 | menu:'菜单',
8 | },
9 | menu1:{
10 | menu1:'菜单1',
11 | },
12 | menu2:{
13 | menu2:'菜单2'
14 | },
15 | menu3:{
16 | menu3:'菜单3'
17 | },
18 | menu4:{
19 | menu4:'菜单4'
20 | }
21 | }
--------------------------------------------------------------------------------
/src/lang/modules/profile/index_en.js:
--------------------------------------------------------------------------------
1 | // en
2 | export default {
3 | profile:{
4 | profile:'profile'
5 | },
6 | profileIndex:{
7 | profileIndex:'profileIndex'
8 | }
9 | }
--------------------------------------------------------------------------------
/src/lang/modules/profile/index_zh.js:
--------------------------------------------------------------------------------
1 | // zh
2 | export default {
3 | profile:{
4 | profile:'个人中心'
5 | },
6 | profileIndex:{
7 | profileIndex:'个人资料'
8 | }
9 | }
--------------------------------------------------------------------------------
/src/lang/zh.js:
--------------------------------------------------------------------------------
1 | //zh
2 | const modulesFiles = import.meta.globEager("./modules/*/index_zh.js");
3 | const modules = Object.keys(modulesFiles).reduce((modules, modulePath) => {
4 | const value = modulesFiles[modulePath];
5 | Object.assign(modules, value.default);
6 | return modules;
7 | }, {});
8 |
9 | export default {
10 | app: {
11 | home: "首页",
12 | setting_title: "应用设置",
13 | },
14 | userDropdown: {
15 | userinfo: "个人资料",
16 | loginout: "退出",
17 | },
18 | login: {
19 | login: "登录",
20 | username: "用户名",
21 | password: "密码",
22 | usernamePlaceholder: "请输入用户名",
23 | passwordPlaceholder: "请输入密码",
24 | loginBtn: "登录",
25 | },
26 | 401: {
27 | 401: "401",
28 | },
29 | 404: {
30 | 404: "404",
31 | },
32 | ...modules,
33 | };
34 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import router from "./router/index";
3 | import store from "./store";
4 | import ElementPlus from "element-plus";
5 | import "element-plus/dist/index.css";
6 | import "./styles/color.scss";
7 | import 'remixicon/fonts/remixicon.css'
8 | import "../mock";
9 | import i18n from "./lang/index";
10 | import App from "./App.vue";
11 | const app = createApp(App);
12 | app.use(ElementPlus, { size: "default", zIndex: 3000 });
13 | app.use(router);
14 | app.use(store);
15 | app.use(i18n);
16 | app.mount("#app");
17 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | //1.引入vue-router
2 | import { createRouter, createWebHashHistory } from "vue-router";
3 | import Cookies from "js-cookie";
4 | import store from "../store";
5 | import { filterAsyncRoutes, sameLevelRoutes } from "../utils/index";
6 | import i18n from "../lang/index";
7 | import NProgress from "nprogress"; // progress bar
8 | import "nprogress/nprogress.css"; // progress bar style
9 | // 2. 定义一些路由
10 | // 每个路由都需要映射到一个组件。
11 | export const constantRoutes = [
12 | {
13 | path: "/login",
14 | name: "login",
15 | meta: { title: "login", icon: "el-icon-menu" },
16 | component: () => import("~/views/login/index.vue"),
17 | hidden: true,
18 | },
19 | {
20 | path: "/401",
21 | name: "401",
22 | meta: { title: "401", icon: "el-icon-menu" },
23 | component: () => import("~/views/401/index.vue"),
24 | hidden: true,
25 | },
26 | {
27 | path: "/404",
28 | name: "404",
29 | meta: { title: "404", icon: "el-icon-menu" },
30 | component: () => import("~/views/404/index.vue"),
31 | hidden: true,
32 | },
33 | ];
34 |
35 | // 3. 创建路由实例并传递 `routes` 配置
36 | // 你可以在这里输入更多的配置,但我们在这里
37 | // 暂时保持简单
38 | const router = createRouter({
39 | // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
40 | history: createWebHashHistory(),
41 | routes: constantRoutes,
42 | });
43 |
44 | //导航守卫中不会删除路由的白名单,对应上面constantRoutes(本地默认路由)名称
45 | const WhiteList = ["login", "401", "404"];
46 | //5.此处可以添加全局路由守卫以方便鉴权,也可以独立鉴权文件然后在main.js引入即可!
47 | //我在这里直接放在路由文件中
48 |
49 | // 前置守卫:路由跳转之前
50 | // to 要进入的路由
51 | // from 从那个路由过来的
52 | router.beforeEach(async (to, form) => {
53 | // start progress bar
54 | NProgress.start();
55 | // 动态修改网页标题
56 | if (to.meta.title) {
57 | const { t } = i18n.global;
58 | document.title = t(`${to.meta.title}.${to.meta.title}`);
59 | }
60 | //执行登录鉴权,未登录跳转登录页
61 | if (!Cookies.get("token")) {
62 | if (to.path == "/login") {
63 | NProgress.done();
64 | return;
65 | } else {
66 | NProgress.done();
67 | return "/login";
68 | }
69 | }
70 | //已登录,如果地址为login执行跳转至控制台
71 | if (to.path == "/login") {
72 | NProgress.done();
73 | return "/dashboard";
74 | }
75 | //是否已同步路由规则
76 | if (!store.state.user.getIsDynamicRoute) {
77 | //获取用户对应菜单权限
78 | const accessRoutes = await store.dispatch("user/getUserRoutes");
79 | // routes must be a non-empty array
80 | if (!accessRoutes || accessRoutes.length <= 0) {
81 | console.log("routes must be a non-null array!");
82 | }
83 | let routes = filterAsyncRoutes(accessRoutes);
84 | // 将三级及以上路由数据拍平成二级
85 | routes.map((item) => {
86 | if (item.children) {
87 | item.children = sameLevelRoutes(item.children, [
88 | {
89 | path: item.path,
90 | title: item.meta.title,
91 | },
92 | ]);
93 | }
94 | });
95 | //根据权限添加路由
96 | routes.forEach((item) => {
97 | router.addRoute(item);
98 | });
99 | }
100 | //添加之前判断要跳转的路由是否存在
101 | let has_route = router.hasRoute(to.name);
102 | //判断是否存在执行重定向避免刷新页面404
103 | if (has_route) {
104 | NProgress.done();
105 | return;
106 | } else {
107 | //判断最新路由数组中是否含有当前即将跳转页面
108 | if (
109 | router.getRoutes().findIndex((value) => value.path === to.fullPath) == -1
110 | ) {
111 | NProgress.done();
112 | //不存在,返回404页面
113 | return "/404";
114 | } else {
115 | NProgress.done();
116 | //重定向
117 | return to.fullPath;
118 | }
119 | }
120 | });
121 |
122 | // 全局解析守卫: 同时在所有组件内守卫和异步路由组件被解析之后 和beforeEach区别是在导航被确认之前
123 | router.beforeResolve((to, form) => {});
124 |
125 | // 后置守卫:路由跳转之后
126 | router.afterEach((to, form) => {
127 | if (to.meta.tabShow) {
128 | store.dispatch("app/addTabs", {
129 | fullPath: to.fullPath,
130 | name: to.name,
131 | meta: to.meta,
132 | });
133 | }
134 | store.dispatch("user/activeRoute", to.fullPath);
135 | //恢复原始keepalive
136 | store.dispatch("user/getKeepAlive");
137 | });
138 |
139 | export default router;
140 |
--------------------------------------------------------------------------------
/src/settings/settings.js:
--------------------------------------------------------------------------------
1 | const settings = {
2 | APP_NAME: "Brisk-Admin", //logo名称
3 | LOGO_GRAM: "Brisk", //logo名称简写
4 | SKIN_CHOOSE: "aside_black_nav_white", //默认皮肤
5 | COLOR_PRIMARY: "#409EFF",//系统主题色
6 | };
7 | export default settings;
8 |
--------------------------------------------------------------------------------
/src/settings/skin.js:
--------------------------------------------------------------------------------
1 | const skin = {
2 | aside_white_nav_white: {
3 | className: "aside_white_nav_white",
4 | asideBackground: "#ffffff",
5 | asideColor: "#000000",
6 | logoBackground: "#ffffff",
7 | logoColor: "#000000",
8 | navBackground: "#ffffff",
9 | navColor: "#000000",
10 | activeColor: "var(--el-color-primary)",
11 | },
12 | aside_black_nav_white: {
13 | className: "aside_black_nav_white",
14 | asideBackground: "#222d32",
15 | asideColor: "#ffffff",
16 | logoColor: "#ffffff",
17 | logoBackground: "#222d32",
18 | navBackground: "#ffffff",
19 | navColor: "#000000",
20 | activeColor: "var(--el-color-primary)",
21 | },
22 | aside_white_nav_black: {
23 | className: "aside_white_nav_black",
24 | asideBackground: "#ffffff",
25 | asideColor: "#000000",
26 | logoColor: "#ffffff",
27 | logoBackground: "#222d32",
28 | navBackground: "#222d32",
29 | navColor: "#ffffff",
30 | activeColor: "var(--el-color-primary)",
31 | },
32 | aside_purple_nav_white: {
33 | className: "aside_purple_nav_white",
34 | asideBackground: "#605ca8",
35 | asideColor: "#ffffff",
36 | logoColor: "#ffffff",
37 | logoBackground: "#605ca8",
38 | navBackground: "#ffffff",
39 | navColor: "#000000",
40 | activeColor: "var(--el-color-primary)",
41 | },
42 | aside_yellow_nav_white: {
43 | className: "aside_yellow_nav_white",
44 | asideBackground: "#f39c12",
45 | asideColor: "#ffffff",
46 | logoColor: "#ffffff",
47 | logoBackground: "#f39c12",
48 | navBackground: "#ffffff",
49 | navColor: "#000000",
50 | activeColor: "var(--el-color-primary)",
51 | },
52 | aside_white_nav_yellow: {
53 | className: "aside_white_nav_yellow",
54 | asideBackground: "#ffffff",
55 | asideColor: "#000000",
56 | logoColor: "#ffffff",
57 | logoBackground: "#f39c12",
58 | navBackground: "#f39c12",
59 | navColor: "#ffffff",
60 | activeColor: "var(--el-color-primary)",
61 | },
62 | };
63 | export default skin;
64 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | language: state => state.app.language,
3 | device: state => state.app.device,
4 | sidebar: state => state.app.sidebar,
5 | showSet: state => state.app.showSet,
6 | routes: state => state.user.routes,
7 | singleRoutes: state => state.user.singleRoutes,
8 | activeRoute: state => state.user.activeRoute,
9 | skinChoose: state => state.settings.skinChoose,
10 | }
11 | export default getters
12 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex";
2 | import getters from "./getters";
3 |
4 | const modulesFiles = import.meta.globEager("./modules/*.js");
5 | const modules = Object.keys(modulesFiles).reduce((modules, modulePath) => {
6 | const moduleName = modulePath.replace(/(.*\/)*([^.]+).*/gi, "$2");
7 | const value = modulesFiles[modulePath];
8 | modules[moduleName] = value.default;
9 | return modules;
10 | }, {});
11 |
12 | const store = createStore({
13 | modules,
14 | getters,
15 | });
16 |
17 | export default store;
18 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import settings from "../../settings/settings";
3 | import router from "~/router/index.js";
4 |
5 | const state = {
6 | sidebar: false,
7 | showSet: false,
8 | device: "desktop",
9 | language: Cookies.get("language")
10 | ? Cookies.get("language")
11 | : settings.LANGUAGE,
12 | tabsList: [],
13 | tabActive: "",
14 | };
15 |
16 | const mutations = {
17 | TOGGLE_DEVICE: (state, device) => {
18 | state.device = device;
19 | },
20 | TOGGLE_SIDEBAR: (state, sidebar) => {
21 | state.sidebar = sidebar;
22 | },
23 | SET_LANGUAGE: (state, language) => {
24 | state.language = language;
25 | Cookies.set("language", language);
26 | },
27 | SET_SHOWSET: (state, showSet) => {
28 | state.showSet = showSet;
29 | },
30 | SET_TABACTIVE: (state, tabActive) => {
31 | state.tabActive = tabActive;
32 | },
33 | SET_TABSLIST: (state, tabsList) => {
34 | state.tabsList = tabsList;
35 | },
36 | };
37 |
38 | const actions = {
39 | toggleDevice({ commit }, device) {
40 | commit("TOGGLE_DEVICE", device);
41 | },
42 | toggleSidebar({ commit }, sidebar) {
43 | commit("TOGGLE_SIDEBAR", sidebar);
44 | },
45 | setLanguage({ commit }, language) {
46 | commit("SET_LANGUAGE", language);
47 | },
48 | setShowSet({ commit }, showSet) {
49 | commit("SET_SHOWSET", showSet);
50 | },
51 | //登录重置标签
52 | refTabs({ state, commit }) {
53 | let tabsList = [];
54 | commit("SET_TABACTIVE", "");
55 | commit("SET_TABSLIST", tabsList);
56 | localStorage.setItem("tabsList", JSON.stringify(tabsList));
57 | localStorage.setItem("tabActive", "");
58 | },
59 | //初始化标签
60 | initTabs({ state, commit }) {
61 | let tabsList = JSON.parse(localStorage.getItem("tabsList"));
62 | let tabActive = localStorage.getItem("tabActive");
63 | if (tabsList && tabActive) {
64 | commit("SET_TABACTIVE", tabActive);
65 | commit("SET_TABSLIST", tabsList);
66 | }
67 | },
68 | //添加标签
69 | addTabs({ state, commit }, route) {
70 | let tabsList = state.tabsList;
71 | const isExists = tabsList.some((item) => item.fullPath == route.fullPath);
72 | if (!isExists) {
73 | tabsList.push(route);
74 | }
75 | commit("SET_TABACTIVE", route.fullPath);
76 | commit("SET_TABSLIST", tabsList);
77 | localStorage.setItem("tabsList", JSON.stringify(tabsList));
78 | localStorage.setItem("tabActive", route.fullPath);
79 | },
80 | //点击标签切换选中
81 | clickTab({ state, commit }, index) {
82 | let tab = state.tabsList[index];
83 | commit("SET_TABACTIVE", tab.fullPath);
84 | localStorage.setItem("tabActive", tab.fullPath);
85 | router.push({ path: tab.fullPath });
86 | },
87 | // 关闭其他标签
88 | closeOtherTabs({ state, commit }, route) {
89 | let tabsList = state.tabsList;
90 | tabsList = tabsList.filter((item) => item.fullPath == route.fullPath);
91 | commit("SET_TABSLIST", tabsList);
92 | localStorage.setItem("tabsList", JSON.stringify(tabsList));
93 | },
94 | // 关闭当前页
95 | closeCurrentTab({ state, commit }, obj) {
96 | let tabsList = state.tabsList;
97 | const index = tabsList.findIndex((item) => item.fullPath == obj.fullPath);
98 | tabsList.splice(index, 1);
99 | commit("SET_TABSLIST", tabsList);
100 | localStorage.setItem("tabsList", JSON.stringify(tabsList));
101 | if (obj.type == "current") {
102 | //打开最后一个tab页面
103 | commit("SET_TABACTIVE", tabsList[tabsList.length - 1].fullPath);
104 | localStorage.setItem("tabActive", tabsList[tabsList.length - 1].fullPath);
105 | router.push({ path: tabsList[tabsList.length - 1].fullPath });
106 | }
107 | },
108 | };
109 |
110 | export default {
111 | namespaced: true,
112 | state,
113 | mutations,
114 | actions,
115 | };
116 |
--------------------------------------------------------------------------------
/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import skin from "../../settings/skin";
3 | import settings from "../../settings/settings";
4 | const state = {
5 | appName: settings.APP_NAME, //logo名称
6 | logogram: settings.LOGO_GRAM, //logo名称简写
7 | skinChoose: localStorage.getItem("skinChoose")
8 | ? skin[localStorage.getItem("skinChoose")]
9 | : skin[settings.SKIN_CHOOSE],
10 | colorPrimary: localStorage.getItem("colorPrimary")
11 | ? localStorage.getItem("colorPrimary")
12 | : settings.COLOR_PRIMARY,
13 | // tagsView: true, //是否需要标签栏
14 | };
15 |
16 | const mutations = {
17 | CHANGE_SETTING: (state, { key, value }) => {
18 | if (state.hasOwnProperty(key)) {
19 | state[key] = value;
20 | if (key === "skinChoose") {
21 | localStorage.setItem("skinChoose", value.className);
22 | }
23 | }
24 | },
25 | COLOR_PRIMARY: (state, color) => {
26 | state.color = color;
27 | },
28 | };
29 |
30 | const actions = {
31 | changeSetting({ commit }, data) {
32 | commit("CHANGE_SETTING", data);
33 | },
34 | setColorPrimary({ commit }, color) {
35 | localStorage.setItem("colorPrimary", color);
36 | commit("COLOR_PRIMARY", color);
37 | },
38 | };
39 |
40 | export default {
41 | namespaced: true,
42 | state,
43 | mutations,
44 | actions,
45 | };
46 |
--------------------------------------------------------------------------------
/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | import { authRoutes } from "~/api";
3 | import { singleAsyncRoutes } from "../../utils/index";
4 |
5 | const state = {
6 | token: Cookies.get("token"),
7 | userinfo: Cookies.get("userinfo") ? JSON.parse(Cookies.get("userinfo")) : {},
8 | routes: [],
9 | singleRoutes: [],
10 | activeRoute: Cookies.get("activeRoute") ? Cookies.get("activeRoute") : "/",
11 | keepAliveRoutes: [],
12 | getIsDynamicRoute: false,
13 | };
14 |
15 | const mutations = {
16 | SET_TOKEN: (state, token) => {
17 | state.token = token;
18 | },
19 | SET_USERINFO: (state, userinfo) => {
20 | state.userinfo = userinfo;
21 | },
22 | SET_ROUTES: (state, routes) => {
23 | state.routes = routes;
24 | },
25 | SET_SINGLEROUTES: (state, routes) => {
26 | state.singleRoutes = routes;
27 | },
28 | SET_KEEPALIVEROUTES: (state, routes) => {
29 | state.keepAliveRoutes = routes;
30 | },
31 | SET_ACTIVEROUTE: (state, activeRoute) => {
32 | state.activeRoute = activeRoute;
33 | },
34 | SET_DYNAMICROUTE: (state, status) => {
35 | state.getIsDynamicRoute = status;
36 | },
37 | };
38 |
39 | const actions = {
40 | loginSet({ commit }, userinfo) {
41 | commit("SET_TOKEN", userinfo.token);
42 | commit("SET_USERINFO", userinfo);
43 | Cookies.set("token", userinfo.token);
44 | Cookies.set("userinfo", JSON.stringify(userinfo));
45 | },
46 | loginOutSet({ commit }) {
47 | commit("SET_TOKEN", null);
48 | commit("SET_USERINFO", null);
49 | Cookies.remove("token");
50 | },
51 | activeRoute({ commit }, path) {
52 | commit("SET_ACTIVEROUTE", path);
53 | Cookies.set("activeRoute", path);
54 | },
55 | getUserRoutes({ state, commit, dispatch }) {
56 | let userinfo = state.userinfo;
57 | return new Promise((resolve, reject) => {
58 | authRoutes({
59 | group: userinfo.group,
60 | })
61 | .then((res) => {
62 | let routes = res.data;
63 | commit("SET_ROUTES", routes);
64 | let single = singleAsyncRoutes(routes);
65 | commit("SET_SINGLEROUTES", single);
66 | commit("SET_DYNAMICROUTE", true);
67 | dispatch("getKeepAlive");
68 | resolve(routes);
69 | })
70 | .catch((err) => {
71 | reject(false);
72 | });
73 | });
74 | },
75 | getKeepAlive({ state, commit }) {
76 | let keepAliveRoutes = [];
77 | state.singleRoutes.map((item) => {
78 | if (item.meta.keepAlive) {
79 | keepAliveRoutes.push(item.name);
80 | }
81 | });
82 | commit("SET_KEEPALIVEROUTES", keepAliveRoutes);
83 | },
84 | setKeepAlive({ commit }, keepAliveRoutes) {
85 | commit("SET_KEEPALIVEROUTES", keepAliveRoutes);
86 | },
87 | setDynamicRoute({ commit }, status) {
88 | commit("SET_DYNAMICROUTE", status);
89 | },
90 | };
91 |
92 | export default {
93 | namespaced: true,
94 | state,
95 | mutations,
96 | actions,
97 | };
98 |
--------------------------------------------------------------------------------
/src/styles/color.scss:
--------------------------------------------------------------------------------
1 | /* 此文件要在element主样式文件之后引入 */
2 | /* 用了这种方法后,你不需要重写任何elementplus组件的颜色 */
3 |
4 | :root {
5 | // 这里可以设置你自定义的颜色变量
6 | // 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改
7 | --el-color-primary-dark: #0d84ff;
8 | }
9 |
10 | /* 核心组件的变量,下面这些样式是必须要写的 */
11 |
12 | .el-link.el-link--primary:hover {
13 | color: var(--el-color-primary-light-2) !important;
14 | }
15 |
16 | .el-tag {
17 | --el-tag-bg-color: var(--el-color-primary-light-9);
18 | --el-tag-border-color: var(--el-color-primary-light-8);
19 | --el-tag-text-color: var(--el-color-primary);
20 | --el-tag-hover-color: var(--el-color-primary);
21 | }
22 |
23 | .el-button--default:active {
24 | color: var(--el-color-primary-dark) !important;
25 | border-color: var(--el-color-primary-dark) !important;
26 | }
27 |
28 | .el-button--primary {
29 | --el-button-bg-color: var(--el-color-primary) !important;
30 | --el-button-border-color: var(--el-color-primary) !important;
31 | --el-button-hover-bg-color: var(--el-color-primary-light-2) !important;
32 | --el-button-hover-border-color: var(--el-color-primary-light-2) !important;
33 | --el-button-active-bg-color: var(--el-color-primary-dark) !important;
34 | --el-button-active-border-color: var(--el-color-primary-dark) !important;
35 | }
36 |
37 | // 你也可以降nProgress的颜色改成element主色调
38 | #nprogress {
39 | & .bar {
40 | background-color: var(--el-color-primary) !important;
41 | }
42 | & .peg {
43 | box-shadow: 0 0 10px var(--el-color-primary), 0 0 5px var(--el-color-primary) !important;
44 | }
45 | & .spinner-icon {
46 | border-top-color: var(--el-color-primary);
47 | border-left-color: var(--el-color-primary);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/styles/element/index.scss:
--------------------------------------------------------------------------------
1 | //在这里添加覆盖主题色的scss
2 |
--------------------------------------------------------------------------------
/src/utils/http/axios.js:
--------------------------------------------------------------------------------
1 | import instance from "./index"
2 | /**
3 | * @param {String} method 请求的方法:get、post、delete、put
4 | * @param {String} url 请求的url:
5 | * @param {Object} data 请求的参数
6 | * @param {Object} config 请求的配置
7 | * @returns {Promise} 返回一个promise对象,其实就相当于axios请求数据的返回值
8 | */
9 |
10 | const axios = ({
11 | method,
12 | url,
13 | data,
14 | config
15 | }) => {
16 | method = method.toLowerCase();
17 | if (method == 'post') {
18 | return instance.post(url, data, {...config})
19 | } else if (method == 'get') {
20 | return instance.get(url, {
21 | params: data,
22 | ...config
23 | })
24 | } else if (method == 'delete') {
25 | return instance.delete(url, {
26 | params: data,
27 | ...config
28 | }, )
29 | } else if (method == 'put') {
30 | return instance.put(url, data,{...config})
31 | } else {
32 | console.error('未知的method' + method)
33 | return false
34 | }
35 | }
36 | export default axios;
--------------------------------------------------------------------------------
/src/utils/http/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {
3 | ElLoading,
4 | ElMessage
5 | } from 'element-plus';
6 | //创建axios的一个实例
7 | var instance = axios.create({
8 | baseURL: import.meta.env.VITE_APP_URL, //接口统一域名
9 | timeout: 6000, //设置超时
10 | headers: {
11 | 'Content-Type': 'application/json;charset=UTF-8;',
12 | }
13 | })
14 | let loading;
15 | //正在请求的数量
16 | let requestCount = 0
17 | //显示loading
18 | const showLoading = () => {
19 | if (requestCount === 0 && !loading) {
20 | loading = ElLoading.service({
21 | text: "Loading ",
22 | background: 'rgba(0, 0, 0, 0.7)',
23 | spinner: 'el-icon-loading',
24 | })
25 | }
26 | requestCount++;
27 | }
28 | //隐藏loading
29 | const hideLoading = () => {
30 | requestCount--
31 | if (requestCount == 0) {
32 | loading.close()
33 | }
34 | }
35 |
36 | //请求拦截器
37 | instance.interceptors.request.use((config) => {
38 | showLoading()
39 | // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
40 | const token = window.localStorage.getItem('token');
41 | token && (config.headers.Authorization = token)
42 | //若请求方式为post,则将data参数转为JSON字符串
43 | if (config.method === 'POST') {
44 | config.data = JSON.stringify(config.data);
45 | }
46 | return config;
47 | }, (error) =>
48 | // 对请求错误做些什么
49 | Promise.reject(error));
50 |
51 | //响应拦截器
52 | instance.interceptors.response.use((response) => {
53 | hideLoading()
54 | //响应成功
55 | return response.data;
56 | }, (error) => {
57 | console.log(error)
58 | //响应错误
59 | if (error.response && error.response.status) {
60 | const status = error.response.status
61 | switch (status) {
62 | case 401:
63 | message = '没有权限';
64 | break;
65 | case 404:
66 | message = '请求地址出错';
67 | break;
68 | case 408:
69 | message = '请求超时';
70 | break;
71 | case 500:
72 | message = '服务器内部错误!';
73 | break;
74 | default:
75 | message = '请求失败'
76 | }
77 | ElMessage.error(message);
78 | return Promise.reject(error);
79 | }
80 | return Promise.reject(error);
81 | });
82 |
83 |
84 | export default instance;
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import { h } from "vue";
2 | import { isExternal } from "./validate";
3 | //先进行views下所有vue文件动态导入声明,以便后台动态返回路由进行本地动态导入,目前这个只支持一层文件夹,如需多层请追加 /*
4 | const modules = import.meta.glob("../views/*/*.vue");
5 | /**
6 | * 转化路由component实例化本地文件
7 | * @param routes asyncRoutes
8 | */
9 | export function filterAsyncRoutes(routes) {
10 | const res = [];
11 | routes.forEach((route) => {
12 | let tmp = { ...route };
13 | if (tmp.children) {
14 | tmp.children = filterAsyncRoutes(tmp.children);
15 | }
16 | if (tmp.component == "noComponent") {
17 | tmp.component = modules[`../views/layout/nestedComponent.vue`];
18 | } else {
19 | tmp.component = modules[`../views/${route.component}`];
20 | }
21 | res.push(tmp);
22 | });
23 | return res;
24 | }
25 |
26 | /**
27 | * 将多层嵌套路由处理成平级
28 | * @param routes asyncRoutes
29 | */
30 | export function sameLevelRoutes(routes, breadcrumb, baseUrl = "") {
31 | let res = [];
32 | routes.forEach((route) => {
33 | const tmp = { ...route };
34 | if (tmp.children) {
35 | let childrenBaseUrl = "";
36 | if (baseUrl == "") {
37 | childrenBaseUrl = tmp.path;
38 | } else if (tmp.path != "") {
39 | childrenBaseUrl = `${baseUrl}/${tmp.path}`;
40 | }
41 | let childrenBreadcrumb = deepClone(breadcrumb);
42 | if (route.meta.breadcrumb !== false) {
43 | childrenBreadcrumb.push({
44 | path: childrenBaseUrl,
45 | title: route.meta.title,
46 | });
47 | }
48 | let tmpRoute = deepClone(route);
49 | tmpRoute.path = childrenBaseUrl;
50 | tmpRoute.meta.breadcrumbNeste = childrenBreadcrumb;
51 | delete tmpRoute.children;
52 | res.push(tmpRoute);
53 | let childrenRoutes = sameLevelRoutes(
54 | tmp.children,
55 | childrenBreadcrumb,
56 | childrenBaseUrl
57 | );
58 | childrenRoutes.map((item) => {
59 | // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
60 | if (res.some((v) => v.path == item.path)) {
61 | res.forEach((v, i) => {
62 | if (v.path == item.path) {
63 | res[i] = item;
64 | }
65 | });
66 | } else {
67 | res.push(item);
68 | }
69 | });
70 | } else {
71 | if (baseUrl != "") {
72 | if (tmp.path != "") {
73 | tmp.path = `${baseUrl}/${tmp.path}`;
74 | } else {
75 | tmp.path = baseUrl;
76 | }
77 | }
78 | // 处理面包屑导航
79 | let tmpBreadcrumb = deepClone(breadcrumb);
80 | if (tmp.meta.breadcrumb !== false) {
81 | tmpBreadcrumb.push({
82 | path: tmp.path,
83 | title: tmp.meta.title,
84 | });
85 | }
86 | tmp.meta.breadcrumbNeste = tmpBreadcrumb;
87 | res.push(tmp);
88 | }
89 | });
90 | return res;
91 | }
92 |
93 | /**
94 | * 多维路由转化一维
95 | * @param routes asyncRoutes
96 | */
97 | export function singleAsyncRoutes(routes) {
98 | var res = [];
99 | routes.forEach((route) => {
100 | let tmp = {
101 | ...route,
102 | };
103 | res.push(tmp);
104 | let path_text = tmp.path;
105 | if (tmp.children) {
106 | let arr = singleAsyncRoutes(tmp.children);
107 | arr.forEach((child) => {
108 | //判断是否是外部链接
109 | if (!isExternal(child.path)) {
110 | //判断path最后一位是否为/
111 | if (path_text.substr(path_text.length - 1, 1) != "/") {
112 | child.path = path_text + "/" + child.path;
113 | } else {
114 | child.path = path_text + child.path;
115 | }
116 | }
117 | });
118 | res = res.concat(arr);
119 | }
120 | });
121 | return res;
122 | }
123 | /**
124 | * 深拷贝
125 | */
126 | export function deepClone(target) {
127 | // 定义一个变量
128 | let result;
129 | // 如果当前需要深拷贝的是一个对象的话
130 | if (typeof target === "object") {
131 | // 如果是一个数组的话
132 | if (Array.isArray(target)) {
133 | result = []; // 将result赋值为一个数组,并且执行遍历
134 | for (let i in target) {
135 | // 递归克隆数组中的每一项
136 | result.push(deepClone(target[i]));
137 | }
138 | // 判断如果当前的值是null的话;直接赋值为null
139 | } else if (target === null) {
140 | result = null;
141 | // 判断如果当前的值是一个RegExp对象的话,直接赋值
142 | } else if (target.constructor === RegExp) {
143 | result = target;
144 | } else {
145 | // 否则是普通对象,直接for in循环,递归赋值对象的所有值
146 | result = {};
147 | for (let i in target) {
148 | result[i] = deepClone(target[i]);
149 | }
150 | }
151 | // 如果不是对象的话,就是基本数据类型,那么直接赋值
152 | } else {
153 | result = target;
154 | }
155 | // 返回最终结果
156 | return result;
157 | }
158 | //用户修复element-plus主题色变更某些组件不改变问题
159 | export const mix = (color1, color2, weight) => {
160 | weight = Math.max(Math.min(Number(weight), 1), 0);
161 | let r1 = parseInt(color1.substring(1, 3), 16);
162 | let g1 = parseInt(color1.substring(3, 5), 16);
163 | let b1 = parseInt(color1.substring(5, 7), 16);
164 | let r2 = parseInt(color2.substring(1, 3), 16);
165 | let g2 = parseInt(color2.substring(3, 5), 16);
166 | let b2 = parseInt(color2.substring(5, 7), 16);
167 | let r = Math.round(r1 * (1 - weight) + r2 * weight);
168 | let g = Math.round(g1 * (1 - weight) + g2 * weight);
169 | let b = Math.round(b1 * (1 - weight) + b2 * weight);
170 | r = ("0" + (r || 0).toString(16)).slice(-2);
171 | g = ("0" + (g || 0).toString(16)).slice(-2);
172 | b = ("0" + (b || 0).toString(16)).slice(-2);
173 | return "#" + r + g + b;
174 | }
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} path
3 | * @returns {Boolean}
4 | */
5 | export function isExternal(path) {
6 | return /^(https?:|mailto:|tel:)/.test(path)
7 | }
8 |
9 | /**
10 | * @param {string} str
11 | * @returns {Boolean}
12 | */
13 | export function validUsername(str) {
14 | const valid_map = ['admin', 'editor']
15 | return valid_map.indexOf(str.trim()) >= 0
16 | }
17 |
18 | /**
19 | * @param {string} url
20 | * @returns {Boolean}
21 | */
22 | export function validURL(url) {
23 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
24 | return reg.test(url)
25 | }
26 |
27 | /**
28 | * @param {string} str
29 | * @returns {Boolean}
30 | */
31 | export function validLowerCase(str) {
32 | const reg = /^[a-z]+$/
33 | return reg.test(str)
34 | }
35 |
36 | /**
37 | * @param {string} str
38 | * @returns {Boolean}
39 | */
40 | export function validUpperCase(str) {
41 | const reg = /^[A-Z]+$/
42 | return reg.test(str)
43 | }
44 |
45 | /**
46 | * @param {string} str
47 | * @returns {Boolean}
48 | */
49 | export function validAlphabets(str) {
50 | const reg = /^[A-Za-z]+$/
51 | return reg.test(str)
52 | }
53 |
54 | /**
55 | * @param {string} email
56 | * @returns {Boolean}
57 | */
58 | export function validEmail(email) {
59 | const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
60 | return reg.test(email)
61 | }
62 |
63 | /**
64 | * @param {string} str
65 | * @returns {Boolean}
66 | */
67 | export function isString(str) {
68 | if (typeof str === 'string' || str instanceof String) {
69 | return true
70 | }
71 | return false
72 | }
73 |
74 | /**
75 | * @param {Array} arg
76 | * @returns {Boolean}
77 | */
78 | export function isArray(arg) {
79 | if (typeof Array.isArray === 'undefined') {
80 | return Object.prototype.toString.call(arg) === '[object Array]'
81 | }
82 | return Array.isArray(arg)
83 | }
84 |
--------------------------------------------------------------------------------
/src/vendor/Export2Excel.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { saveAs } from 'file-saver'
3 | import XLSX from 'xlsx'
4 |
5 | function generateArray(table) {
6 | var out = [];
7 | var rows = table.querySelectorAll('tr');
8 | var ranges = [];
9 | for (var R = 0; R < rows.length; ++R) {
10 | var outRow = [];
11 | var row = rows[R];
12 | var columns = row.querySelectorAll('td');
13 | for (var C = 0; C < columns.length; ++C) {
14 | var cell = columns[C];
15 | var colspan = cell.getAttribute('colspan');
16 | var rowspan = cell.getAttribute('rowspan');
17 | var cellValue = cell.innerText;
18 | if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
19 |
20 | //Skip ranges
21 | ranges.forEach(function (range) {
22 | if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
23 | for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
24 | }
25 | });
26 |
27 | //Handle Row Span
28 | if (rowspan || colspan) {
29 | rowspan = rowspan || 1;
30 | colspan = colspan || 1;
31 | ranges.push({
32 | s: {
33 | r: R,
34 | c: outRow.length
35 | },
36 | e: {
37 | r: R + rowspan - 1,
38 | c: outRow.length + colspan - 1
39 | }
40 | });
41 | };
42 |
43 | //Handle Value
44 | outRow.push(cellValue !== "" ? cellValue : null);
45 |
46 | //Handle Colspan
47 | if (colspan)
48 | for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
49 | }
50 | out.push(outRow);
51 | }
52 | return [out, ranges];
53 | };
54 |
55 | function datenum(v, date1904) {
56 | if (date1904) v += 1462;
57 | var epoch = Date.parse(v);
58 | return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
59 | }
60 |
61 | function sheet_from_array_of_arrays(data, opts) {
62 | var ws = {};
63 | var range = {
64 | s: {
65 | c: 10000000,
66 | r: 10000000
67 | },
68 | e: {
69 | c: 0,
70 | r: 0
71 | }
72 | };
73 | for (var R = 0; R != data.length; ++R) {
74 | for (var C = 0; C != data[R].length; ++C) {
75 | if (range.s.r > R) range.s.r = R;
76 | if (range.s.c > C) range.s.c = C;
77 | if (range.e.r < R) range.e.r = R;
78 | if (range.e.c < C) range.e.c = C;
79 | var cell = {
80 | v: data[R][C]
81 | };
82 | if (cell.v == null) continue;
83 | var cell_ref = XLSX.utils.encode_cell({
84 | c: C,
85 | r: R
86 | });
87 |
88 | if (typeof cell.v === 'number') cell.t = 'n';
89 | else if (typeof cell.v === 'boolean') cell.t = 'b';
90 | else if (cell.v instanceof Date) {
91 | cell.t = 'n';
92 | cell.z = XLSX.SSF._table[14];
93 | cell.v = datenum(cell.v);
94 | } else cell.t = 's';
95 |
96 | ws[cell_ref] = cell;
97 | }
98 | }
99 | if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
100 | return ws;
101 | }
102 |
103 | function Workbook() {
104 | if (!(this instanceof Workbook)) return new Workbook();
105 | this.SheetNames = [];
106 | this.Sheets = {};
107 | }
108 |
109 | function s2ab(s) {
110 | var buf = new ArrayBuffer(s.length);
111 | var view = new Uint8Array(buf);
112 | for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
113 | return buf;
114 | }
115 |
116 | export function export_table_to_excel(id) {
117 | var theTable = document.getElementById(id);
118 | var oo = generateArray(theTable);
119 | var ranges = oo[1];
120 |
121 | /* original data */
122 | var data = oo[0];
123 | var ws_name = "SheetJS";
124 |
125 | var wb = new Workbook(),
126 | ws = sheet_from_array_of_arrays(data);
127 |
128 | /* add ranges to worksheet */
129 | // ws['!cols'] = ['apple', 'banan'];
130 | ws['!merges'] = ranges;
131 |
132 | /* add worksheet to workbook */
133 | wb.SheetNames.push(ws_name);
134 | wb.Sheets[ws_name] = ws;
135 |
136 | var wbout = XLSX.write(wb, {
137 | bookType: 'xlsx',
138 | bookSST: false,
139 | type: 'binary'
140 | });
141 |
142 | saveAs(new Blob([s2ab(wbout)], {
143 | type: "application/octet-stream"
144 | }), "test.xlsx")
145 | }
146 |
147 | export function export_json_to_excel({
148 | multiHeader = [],
149 | header,
150 | data,
151 | filename,
152 | merges = [],
153 | autoWidth = true,
154 | bookType = 'xlsx'
155 | } = {}) {
156 | /* original data */
157 | filename = filename || 'excel-list'
158 | data = [...data]
159 | data.unshift(header);
160 |
161 | for (let i = multiHeader.length - 1; i > -1; i--) {
162 | data.unshift(multiHeader[i])
163 | }
164 |
165 | var ws_name = "SheetJS";
166 | var wb = new Workbook(),
167 | ws = sheet_from_array_of_arrays(data);
168 |
169 | if (merges.length > 0) {
170 | if (!ws['!merges']) ws['!merges'] = [];
171 | merges.forEach(item => {
172 | ws['!merges'].push(XLSX.utils.decode_range(item))
173 | })
174 | }
175 |
176 | if (autoWidth) {
177 | /*设置worksheet每列的最大宽度*/
178 | const colWidth = data.map(row => row.map(val => {
179 | /*先判断是否为null/undefined*/
180 | if (val == null) {
181 | return {
182 | 'wch': 10
183 | };
184 | }
185 | /*再判断是否为中文*/
186 | else if (val.toString().charCodeAt(0) > 255) {
187 | return {
188 | 'wch': val.toString().length * 2
189 | };
190 | } else {
191 | return {
192 | 'wch': val.toString().length
193 | };
194 | }
195 | }))
196 | /*以第一行为初始值*/
197 | let result = colWidth[0];
198 | for (let i = 1; i < colWidth.length; i++) {
199 | for (let j = 0; j < colWidth[i].length; j++) {
200 | if (result[j]['wch'] < colWidth[i][j]['wch']) {
201 | result[j]['wch'] = colWidth[i][j]['wch'];
202 | }
203 | }
204 | }
205 | ws['!cols'] = result;
206 | }
207 |
208 | /* add worksheet to workbook */
209 | wb.SheetNames.push(ws_name);
210 | wb.Sheets[ws_name] = ws;
211 |
212 | var wbout = XLSX.write(wb, {
213 | bookType: bookType,
214 | bookSST: false,
215 | type: 'binary'
216 | });
217 | saveAs(new Blob([s2ab(wbout)], {
218 | type: "application/octet-stream"
219 | }), `${filename}.${bookType}`);
220 | }
221 |
--------------------------------------------------------------------------------
/src/views/401/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/src/views/404/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/src/views/admin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {{$root.$t('admin.field.'+item.label)}}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {{ scope.row.group.name }}
81 |
82 |
83 |
84 |
85 | {{ scope.row.status_text }}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | {{ $root.$t(scope.row.name) }}
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | {{$root.$t('admin.field.'+item.label)}}
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
265 |
266 |
268 |
--------------------------------------------------------------------------------
/src/views/adminGroup/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{$root.$t('adminGroup.field.'+item.label)}}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {{ scope.row.status_text }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | {{$root.$t('adminGroup.field.'+item.label)}}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
191 |
192 |
195 |
--------------------------------------------------------------------------------
/src/views/adminLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {{$root.$t('adminLog.field.'+item.label)}}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | {{ scope.row.status_text }}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | {{ $root.$t(scope.row.name) }}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
243 |
244 |
247 |
--------------------------------------------------------------------------------
/src/views/adminRule/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | {{$root.$t('adminRule.field.'+item.label)}}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {{ scope.row.status_text }}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | {{ $root.$t(scope.row.name) }}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {{$root.$t('adminRule.field.'+item.label)}}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
215 |
216 |
219 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ $t("dashboard.totalUserNumber") }}
7 |
6666
8 |
9 |
10 |
11 |
12 |
{{ $t("dashboard.totalVisits") }}
13 |
6666
14 |
15 |
16 |
17 |
18 |
{{ $t("dashboard.totalOrderNumber") }}
19 |
6666
20 |
21 |
22 |
23 |
24 |
{{ $t("dashboard.totalSalesAmount") }}
25 |
6666
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
218 |
219 |
241 |
--------------------------------------------------------------------------------
/src/views/layout/components/aside/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ appName }}
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 | {{ appName }}
16 | {{ logogram }}
17 |
18 |
19 |
22 |
23 |
24 |
25 |
49 |
60 |
--------------------------------------------------------------------------------
/src/views/layout/components/aside/menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/layout/components/aside/menuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t(item.name + "." + item.meta.title) }}
6 |
7 |
8 |
9 |
10 |
11 | {{ $t(item.name + "." + item.meta.title) }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
53 |
54 |
--------------------------------------------------------------------------------
/src/views/layout/components/header/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
56 |
57 |
109 |
110 |
--------------------------------------------------------------------------------
/src/views/layout/components/setting/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 系统设置
5 |
6 |
7 |
8 |
9 |
10 |
系统主题色
11 |
12 |
13 |
14 |
15 |
16 |
系统皮肤
17 |
18 |
19 |
20 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
55 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
87 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
103 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
169 |
170 |
--------------------------------------------------------------------------------
/src/views/layout/components/tabs/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ $t(item.name + "." + item.meta.title) }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | 关闭当前
22 | 关闭其他
23 |
24 |
25 |
26 |
27 |
28 |
29 |
92 |
93 |
--------------------------------------------------------------------------------
/src/views/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
91 |
92 |
--------------------------------------------------------------------------------
/src/views/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '~/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route(route) {
9 |
10 | }
11 | },
12 | beforeMount() {
13 | window.addEventListener('resize', this.$_resizeHandler)
14 | },
15 | beforeUnmount() {
16 | window.removeEventListener('resize', this.$_resizeHandler)
17 | },
18 | mounted() {
19 | const isMobile = this.$_isMobile()
20 | if (isMobile) {
21 | store.dispatch('app/toggleDevice', 'mobile')
22 | store.dispatch('app/toggleSidebar', false)
23 | } else {
24 | store.dispatch('app/toggleDevice', 'desktop')
25 | store.dispatch('app/toggleSidebar', false)
26 | }
27 | },
28 | methods: {
29 | // use $_ for mixins properties
30 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
31 | $_isMobile() {
32 | const rect = body.getBoundingClientRect()
33 | return rect.width - 1 < WIDTH
34 | },
35 | $_resizeHandler() {
36 | if (!document.hidden) {
37 | const isMobile = this.$_isMobile()
38 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
39 |
40 | if (isMobile) {
41 | store.dispatch('app/toggleSidebar', false)
42 | } else {
43 | store.dispatch('app/toggleSidebar', false)
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/views/layout/nestedComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{
26 | $t("login.loginBtn")
27 | }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
74 |
75 |
--------------------------------------------------------------------------------
/src/views/menu1/index.vue:
--------------------------------------------------------------------------------
1 |
2 | menu1
3 |
4 |
5 |
6 |
7 |
20 |
21 |
--------------------------------------------------------------------------------
/src/views/menu2/index.vue:
--------------------------------------------------------------------------------
1 |
2 | menu2
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/menu3/index.vue:
--------------------------------------------------------------------------------
1 |
2 | menu3
3 |
4 |
5 |
6 |
7 |
20 |
21 |
--------------------------------------------------------------------------------
/src/views/menu4/index.vue:
--------------------------------------------------------------------------------
1 |
2 | menu4
3 |
4 |
5 |
6 |
7 |
20 |
21 |
--------------------------------------------------------------------------------
/src/views/profile/index.vue:
--------------------------------------------------------------------------------
1 |
2 | profile
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import vue from "@vitejs/plugin-vue";
3 | import ElementPlus from "unplugin-element-plus/vite";
4 | const { resolve } = require("path");
5 | /**
6 | * @type {import('vite').UserConfig}
7 | */
8 | export default defineConfig(({ command, mode }) => {
9 | let vueI18n = {};
10 | if (command === "serve") {
11 | vueI18n["vue-i18n"] = "vue-i18n/dist/vue-i18n.cjs.js"; //解决dev运行警告You are running the esm-bundler build of vue-i18n.
12 | }
13 | return {
14 | plugins: [
15 | vue(),
16 | ElementPlus({
17 | useSource: true,
18 | }),
19 | ],
20 | resolve: {
21 | alias: {
22 | "~/": `${resolve(__dirname, "src")}/`,
23 | ...vueI18n,
24 | },
25 | },
26 | // base: "./",
27 | server: {
28 | host: "127.0.0.1",
29 | port: 8086,
30 | open: true,
31 | // 反向代理
32 | proxy: {
33 | "/api": {
34 | target: "http://127.0.0.1:80/",
35 | changeOrigin: true,
36 | rewrite: (path) => path.replace(/^\/api/, ""),
37 | },
38 | },
39 | },
40 | css: {
41 | preprocessorOptions: {
42 | scss: {
43 | additionalData: `@use "~/styles/element/index.scss" as *;`,
44 | },
45 | },
46 | },
47 | };
48 | });
49 |
--------------------------------------------------------------------------------