116 Commits

Author SHA1 Message Date
lyt
73db2121ff 'admin-21.12.21:更新优化内容请看CHANGELOG.md文件' 2021-12-21 21:35:01 +08:00
lyt
9051c31771 'admin-21.12.21:更新优化内容请看CHANGELOG.md文件' 2021-12-21 21:32:26 +08:00
lyt
18ef20b230 'admin-21.12.17:修复tagsView拖拽问题,感谢@简单爱、' 2021-12-17 20:19:11 +08:00
lyt
ec5911a987 'admin-21.12.16:优化登录页、添加开发文档连接' 2021-12-16 21:10:17 +08:00
lyt
646eac1c88 'admin-21.12.12:更新修复查看CHANGELOG.md文件' 2021-12-12 21:27:33 +08:00
94af6f8e1e !15 fixed typo
Merge pull request !15 from Rehmet/master
2021-12-12 04:39:01 +00:00
f9c604ac96 update README.md. fixed typo 2021-12-11 09:52:43 +00:00
lyt
4228b58434 'admin-21.12.04:修复更新诸多内容,具体查看CHANGELOG.md文件。改动大,谨慎更新!' 2021-12-04 13:32:14 +08:00
lyt
cc7520976e 'admin-21.10.31:优化login界面组件name值重复问题' 2021-10-31 22:49:53 +08:00
lyt
e3aa4d4816 'admin-21.10.30:修复演示错误,TagsView拖拽问题,感谢群友@小明画家、@HelloWord' 2021-10-30 21:11:40 +08:00
lyt
af13922492 'admin-21.10.18:优化tagsview问题' 2021-10-18 20:32:06 +08:00
lyt
6467f57cf2 'admin-21.10.18:优化tagsview问题' 2021-10-18 20:21:22 +08:00
lyt
04ca9b04c1 'admin-21.10.17:更新优化,具体查看根目录CHANGELOG.md' 2021-10-17 12:32:28 +08:00
lyt
3f4c08c119 'admin-21.09.25:修复横向导航菜单不高亮问题' 2021-09-25 19:11:58 +08:00
lyt
99431138c5 'admin-21.09.25:修改浏览器窗口大小改变时菜单水平折叠问题,感谢群友@三木' 2021-09-25 17:47:34 +08:00
lyt
fe70746902 'admin-21.09.25:更新修复诸多内容,请查看CHANGELOG.md' 2021-09-25 16:47:30 +08:00
lyt
cea507e688 'admin-21.09.11:优化深色模式' 2021-09-11 10:37:29 +08:00
15a3025928 update CHANGELOG.md. 2021-09-10 15:32:18 +00:00
lyt
4f9ddc6c26 'admin-21.09.10:新增功能,具体查看CHANGELOG.md' 2021-09-10 23:24:58 +08:00
f6ff10c4a9 'admin-21.08.29:修复优化诸多内容,请查看CHANGELOG.md文件' 2021-08-29 18:45:29 +08:00
8b1c80a8d4 'admin-21.08.22:修复优化诸多内容,请查看CHANGELOG.md文件' 2021-08-22 18:05:26 +08:00
a4da51c179 !14 修复固定header后没有回到顶部的bug,修复拉取项目后运行不起来的bug
Merge pull request !14 from wjs0509/master
2021-08-21 09:42:43 +00:00
12e6b2b4bc 修复用户管理分页的bug 2021-08-20 19:16:29 +08:00
25fa3536de 修复固定header后没有回到顶部的bug,修复拉取项目后运行不起来的bug 2021-08-20 18:58:52 +08:00
ea5dccebe6 'admin-21.08.16:优化布局配置菜单/顶栏功能' 2021-08-16 21:44:16 +08:00
e0960f9009 'admin-21.08.14优化修复菜单高亮、右键菜单全屏、国际化等' 2021-08-14 18:03:50 +08:00
c49736ae7d 'admin-21.08.07:更新README.md' 2021-08-07 19:43:33 +08:00
dfcf817d70 'admin-21.08.06:优化,具体查看根目录CHANGELOG.md文件' 2021-08-06 12:19:25 +08:00
71eb623cef 'admin-21.08.06:优化,具体查看根目录CHANGELOG.md文件' 2021-08-06 12:17:56 +08:00
28edd79b1c 'admin-21.08.01:更新修复请查看CHANGELOG.md文件或查看标签' 2021-08-01 18:30:30 +08:00
1dd6b653e7 处理图标页全屏后无法渲染的问题 2021-07-27 02:36:19 +00:00
92db3f6335 !12 处理图表页全屏后无法渲染的问题
Merge pull request !12 from MaxNull/dev-20210727
2021-07-27 02:33:54 +00:00
fd5d763de4 处理图标页全屏后无法渲染的问题 2021-07-27 10:13:16 +08:00
0050120d79 Merge branch 'master' of https://gitee.com/maxnull/vue-next-admin
# Conflicts:
#	src/layout/component/aside.vue
#	src/layout/component/header.vue
#	src/layout/navBars/tagsView/tagsView.vue
#	src/store/modules/tagsViewRoutes.ts
2021-07-27 08:51:34 +08:00
b86aca5838 'admin-21.07.26:处理全屏若干问题,pr!11,感谢群友@另一个前端' 2021-07-26 23:46:37 +08:00
a1be0533c4 '处理冲突,请勿拉新(请忽略此版本)' 2021-07-26 20:12:51 +08:00
6ca702d590 处理全屏若干问题 2021-07-26 17:31:26 +08:00
lyt
e3f3a9cc5e 'admin-21.07.26:修复tagsview右键菜单点击开启当前页全屏问题' 2021-07-26 17:00:04 +08:00
0e45548c06 'admin-21.07.25:新增数据可视化演示2、登录页新增扫码' 2021-07-25 01:28:32 +08:00
3e7ff893cd 'admin-21.07.16:优化数据可视化演示、tagsView移动端拖动问题' 2021-07-16 23:59:56 +08:00
lyt
7138d5a24d 'admin-21.07.16:发布v1.0.12版本,具体更新内容查看CHANGELOG.md' 2021-07-16 12:46:26 +08:00
lyt
804213a646 'admin-21.07.15:优化tagsView动态路由时的右键菜单' 2021-07-15 10:53:16 +08:00
d9e9779581 'admin-21.07.14:优化、修复诸多内容,具体查看根目录CHANGELOG.md文件' 2021-07-14 21:02:28 +08:00
f7275035e5 'admin-21.07.08:clipboard切换为vue-clipboard3(#10),@21030442-mao' 2021-07-08 20:52:48 +08:00
2303c4a462 !10 clipboard切换为vue-clipboard3
Merge pull request !10 from MaxNull/master
2021-07-08 12:42:04 +00:00
e4ad584724 Merge branch 'master' of https://gitee.com/lyt-top/vue-next-admin 2021-07-08 17:27:12 +08:00
6c28fb5211 clipboard切换为vue-clipboard3 2021-07-08 17:27:01 +08:00
lyt
ff9d32c2e9 'admin-21.07.08:修复登录页表单在移动端显示问题' 2021-07-08 16:45:10 +08:00
b3a6bb6a8e 'admin-21.07.07:优化内嵌iframe、外链,解决tagsView刷新问题' 2021-07-07 20:16:31 +08:00
lyt
ae27489247 'admin-21.07.07:更新最新依赖' 2021-07-07 17:15:11 +08:00
8ff747f011 'admin-21.07.04:修复图标选择器双向绑定回显、路由引入第三方icon样式表现不一致的问题,感谢群友@伯牙已遇钟子期、@借个微笑丶' 2021-07-04 01:09:37 +08:00
dc36d3b2a4 Merge pull request !9 from 真的不知所措/hotfix 2021-07-03 17:00:01 +00:00
e28760d921 修复路由引入第三方icon样式表现不一致的问题 2021-07-03 21:28:22 +08:00
543a31b6d1 'admin-07.03:修复图标选择器双向绑定回显问题,感谢群友@伯牙已遇钟子期' 2021-07-03 20:29:26 +08:00
03b85bc8b0 'admin-21.07.03:右键菜单文字换行问题' 2021-07-03 17:34:03 +08:00
fe537d0e36 'admin-21.07.02:系列组件优化,请查看CHANGELOG.md更新日志文件' 2021-07-02 21:52:32 +08:00
382454ca0e 'admin-21.07.01:新增滚动通知栏/公告组件及演示界面' 2021-07-01 22:33:57 +08:00
lyt
b2fd7d6010 'admin-21.06.29:更新最新依赖、各项优化,请查看CHANGELOG.md更新文件' 2021-06-29 17:20:40 +08:00
92904ffef3 'admin-21.06.24:修复使用拖动指令出现滚动条的问题' 2021-06-24 19:45:50 +08:00
lyt
0c2026d37e 'admin-21.06.24:新增拖动指令及其演示界面、优化登录页,锁屏界面' 2021-06-24 18:02:13 +08:00
lyt
54bbaa1946 'admin-21.06.23:优化去掉内嵌iframe、其它组件优化等' 2021-06-23 17:29:23 +08:00
8ab22b0a02 'admin-21.06.22:修复内嵌iframe不可使用的问题' 2021-06-22 22:03:59 +08:00
lyt
20e8cad9c7 'admin-21.06.22:@vite2.3.8降低成@vite2.3.7,防止elementPlus字体图标消失' 2021-06-22 10:08:21 +08:00
lyt
79f08c362c 'admin-21.06.21:优化时间工具类,感谢群友@我觉得还行' 2021-06-21 15:49:16 +08:00
2462f110ef 'admin-21.06.19:修复诸多问题,具体查看CHANGELOG.md文件1.04更新日志' 2021-06-20 11:46:51 +08:00
e21fd35b08 'admin-21.06.19:修复诸多问题,具体查看CHANGELOG.md文件1.04更新日志' 2021-06-19 17:53:23 +08:00
9a43e1d6c7 'admin-21.06.19:修复诸多问题,具体查看CHANGELOG.md文件1.04更新日志' 2021-06-19 17:49:42 +08:00
57f666a69e !6 新增 深克隆工具方便开发
Merge pull request !6 from kangert/master
2021-06-18 14:26:56 +08:00
6166da0875 Merge branch 'master' of gitee.com:kangert/vue-next-admin 2021-06-18 11:11:22 +08:00
72ffa2d6fd 新增 深克隆工具方便开发 2021-06-18 11:11:20 +08:00
fa34433aa1 !5 优化 类型定义提高编码体验
Merge pull request !5 from kangert/master
2021-06-17 12:43:17 +08:00
134b63848a 修复 不能将类型“string | undefined”分配给类型“string”的问题 2021-06-17 10:33:15 +08:00
793e740c32 优化 类型定义提高编码体验 2021-06-17 10:32:20 +08:00
1ade90a114 !4 优化 vuex模块自动导入
Merge pull request !4 from kangert/master
2021-06-16 21:51:16 +08:00
6a176cf78a 优化 vuex模块自动导入 2021-06-16 17:32:36 +08:00
lyt
2950720b21 'admin-21.06.03:删除G6思维导图界面' 2021-06-03 11:46:49 +08:00
lyt
1596de0cde 'admin-21.06.01:修复开启后端控制路由isRequestRoutes在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期' 2021-06-02 17:16:41 +08:00
49c6356e35 'admin-21.06.01:修复菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意' 2021-06-01 20:26:35 +08:00
2c1ffb7369 'admin-21.05.31:修复分栏、经典布局路由设置meta.isHide为true时报错问题,感谢群友@29、@芭芭拉' 2021-05-31 21:42:31 +08:00
lyt
80a88495fb 'admin-21.05.31:更正文件命名错误' 2021-05-31 16:10:57 +08:00
lyt
4b82473eb0 'admin-21.05.31:修复分栏布局路由设置meta.isHide为true时报错问题,感谢群友@29' 2021-05-31 16:06:53 +08:00
441a00ed7d 'admin-21.05.29:修复切换布局后面包屑显示异常问题' 2021-05-29 00:29:49 +08:00
lyt
03609aeda6 'admin-21.05.28:修复布局配置设置初始值不生效问题' 2021-05-28 17:23:26 +08:00
4cc95c4f8f 'admin-21.05.27:新增utils下的storage.js读取浏览器缓存新写法,建议使用' 2021-05-27 21:26:45 +08:00
dca87eea7e 'admin-21.05.27:新增utils下的storage.ts读取浏览器缓存新写法,建议使用' 2021-05-27 21:01:41 +08:00
lyt
1bdef58956 'admin-21.05.26:更新最新依赖' 2021-05-26 10:54:28 +08:00
806ef38eea 'admin-21.05.23:修复改变浏览器窗口时,部分布局配置失效问题' 2021-05-23 16:30:14 +08:00
lyt
e51acd0cc1 'admin-21.05.20:根据官方文档,增加eslint的Vue.js3.x的配置,感谢群友@λόγος' 2021-05-20 17:18:26 +08:00
lyt
899dc13024 'admin-21.05.20:修改端口防止端口与百度云冲突,感谢群友@烦。更新最新依赖' 2021-05-20 11:22:29 +08:00
lyt
2063732014 'admin-21.05.18:新增分栏布局分栏导航菜单可设置水平、垂直布局' 2021-05-18 15:42:45 +08:00
fc696e6d94 'admin-21.05.16:优化iframe、更新最新依赖、规范工具类命名' 2021-05-16 17:02:53 +08:00
lyt
be9df6766e 'admin-21.05.13:修复全局改变组件大小默认值报错问题' 2021-05-13 16:41:26 +08:00
lyt
679cac1c74 'admin-21.02.27:vue-next-admin项目第一次提交' 2021-05-13 12:01:44 +08:00
lyt
8b21955e28 'admin-21.02.27:vue-next-admin项目第一次提交' 2021-05-13 12:01:09 +08:00
lyt
051b622730 'admin-21.05.13:删除yarn.lock(会报错),添加package-lock.json等,感谢@mrmengj' 2021-05-13 10:44:11 +08:00
lyt
7e470e4223 'admin-21.05.12:优化全局切换组件size不生效、更新最新依赖等' 2021-05-12 11:32:31 +08:00
lyt
dde15be65c 'admin-21.05.08:优化锁屏界面' 2021-05-08 18:02:13 +08:00
lyt
daaafac7f8 'admin-21.05.08:修复粘贴复制的链接,登录后不跳转到对应的链接的问题' 2021-05-08 11:36:07 +08:00
lyt
7c673b9ae6 'admin-21.05.07:更新最新依赖' 2021-05-07 15:30:00 +08:00
37e3db50b6 'admin-21.05.04:修复ts的interface类型声明及引用报错问题' 2021-05-04 20:51:36 +08:00
6e99cdee7d 'admin-21.05.02:新增树形控件演示、部分逻辑优化' 2021-05-02 21:24:41 +08:00
lyt
a184ebf5d3 'admin-21.04.30:新增波浪效果指令及演示、窗格拆分器等' 2021-04-30 16:15:43 +08:00
lyt
6cf87bee90 'admin-21.04.29:更新最新依赖、新增大图预览及其它组件演示效果优化' 2021-04-29 17:42:01 +08:00
lyt
9863d4ed0d 'admin-21.04.29:新增分栏布局时分栏菜单背景渐变功能' 2021-04-29 11:57:04 +08:00
lyt
7016ef6723 'admin-21.04.29:新增分栏布局时分栏菜单背景渐变功能' 2021-04-29 11:49:01 +08:00
6517b843ae 'admin-21.04.28:修复themeConfig.ts设置默认布局不生效的问题' 2021-04-28 20:20:53 +08:00
lyt
b240c9d8e9 'admin-21.04.28:新增全局改变组件大小功能、拖拽布局演示等' 2021-04-28 17:02:32 +08:00
lyt
fa717eb5f2 'admin-21.04.27:更新最新依赖' 2021-04-27 16:45:37 +08:00
d1e656dc90 'admin-21.04.25:修复打包、切换布局刷新样式丢失问题' 2021-04-25 21:15:44 +08:00
lyt
827c2c6b04 'admin-21.04.23:修复后端控制路由报Invalid-arguments、刷新界面出现404、空白、报错等' 2021-04-23 16:18:17 +08:00
lyt
084e0f2c99 'admin-21.04.23:修复后端控制路由报Invalid-arguments、刷新界面出现404、空白、报错等' 2021-04-23 15:30:32 +08:00
1f4c902f40 'admin-21.04.21:更新最新依赖' 2021-04-21 20:54:47 +08:00
lyt
ea60dd9d93 'admin-21.04.19:修改tsconfig.json,防止开发环境编译器报红问题' 2021-04-19 13:03:36 +08:00
1fe01e62a4 'admin-21.04.18:更新最新依赖' 2021-04-18 16:45:25 +08:00
lyt
b1cf16aa8c 'admin-21.04.15:修改文案、链接等' 2021-04-15 14:16:09 +08:00
70f089acf7 'admin-21.04.14:更新最新依赖、添加eslint规则' 2021-04-14 23:00:38 +08:00
268 changed files with 34540 additions and 7229 deletions

7
.env
View File

@ -1,5 +1,8 @@
# port 端口号
VUE_APP_PORT = 9999
VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器
VUE_APP_OPEN = false
VITE_OPEN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
VITE_PUBLIC_PATH = /vue-next-admin-preview/

View File

@ -1,2 +1,5 @@
# 开发环境
VUE_APP_BASE_API = http://localhost:9999/
# 本地环境
ENV = 'development'
# 本地环境接口地址
VITE_API_URL = 'http://localhost:8888/'

View File

@ -1,2 +1,5 @@
# 线上环境
VUE_APP_BASE_API = ''
ENV = 'production'
# 线上环境接口地址
VITE_API_URL = 'https://lyt-top.gitee.io/vue-next-admin-preview/'

View File

@ -1,3 +1,4 @@
*.sh
node_modules
lib
@ -5,7 +6,6 @@ lib
*.scss
*.woff
*.ttf
*.json
.vscode
.idea
dist

View File

@ -1,22 +1,63 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['vue'],
extends: ['plugin:vue/essential', 'eslint:recommended'],
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
'@type-eslint/ban-ts-ignore': 'off',
'@type-eslint/explicit-function-return-type': 'off',
'@type-eslint/no-explicit-any': 'off',
'@type-eslint/no-var-requires': 'off',
'@type-eslint/no-empty-function': 'off',
'@type-eslint/no-use-before-define': 'off',
'@type-eslint/ban-ts-comment': 'off',
'@type-eslint/ban-types': 'off',
'@type-eslint/no-non-null-assertion': 'off',
'@type-eslint/explicit-module-boundary-types': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/html-self-closing': 'off',
'vue/no-multiple-template-root': 'off',
'vue/require-default-prop': 'off',
'vue/no-v-model-argument': 'off',
'vue/no-arrow-functions-in-watch': 'off',
'vue/no-template-key': 'off',
'vue/no-v-html': 'off',
'vue/comment-directive': 'off',
'vue/no-parsing-error': 'off',
'no-unused-vars': 'error',
'no-dupe-args': 'error',
'no-empty': 'off',
'no-extra-semi': 'off',
'no-constant-condition': 'off',
'no-console': 'error',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/multi-word-component-names': 'off',
'no-useless-escape': 'off',
'no-sparse-arrays': 'off',
'no-prototype-builtins': 'off',
'no-constant-condition': 'off',
'no-use-before-define': 'off',
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'generator-star-spacing': 'off',
'no-unreachable': 'off',
'no-multiple-template-root': 'off',
'no-unused-vars': 'error',
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
},
};

5
.gitignore vendored
View File

@ -1,7 +1,8 @@
node_modules
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
@ -19,4 +20,4 @@ pnpm-debug.log*
*.ntvs*
*.njsproj
*.sln
*.sw?
*.sw?

View File

@ -1,124 +1,260 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin" target="_blank">vue-prev-admin 更新日志</a>
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
🎉🎉🔥 `vue-prev-admin` 基于 vue2.x + webpack + element ui适配手机、平板、pc 的后台开源免费模板库vue3.x 请切换 master 分支)
## 1.2.3
`2023.04.12`
- 🐞 修复 [#I6UW2I 关闭标签页后,分栏没有消失,需要手动点击首页才会消失](https://gitee.com/lyt-top/vue-next-admin/issues/I6UW2I),感谢[@小菜鸟儿](https://gitee.com/cainiaoer)
- 🎯 优化 `layout/navBars/breadcrumb` 文件夹名称改成 `layout/navBars/topBar` 更易理解(可全局替换),感谢群友@傲世盛唐
- 🎯 优化 `layout/navBars/topBar/user.vue` 组件,`UserNews` 点击消息图标触发范围,防止点击消息通知背景色时不触发 Popover 弹出框
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 1.2.2
`2023.02.23`
`2021.12.21`
🚩🚩🚩 感谢 [驰骋工作流引擎-表单引擎-低代码开发平台](http://www.ccflow.org/) 赞助商的赞助。驰骋公司为社会提供流程引擎+表单引擎+低代码开发平台一体的开源软件解决方案,欢迎广大开发者前去体验!
- 🎉 新增 赞助商组件(`/src/layout/sponsors`[项目目录结构查看](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/)
- 🎯 优化 `/src/utils/storage``key` 编写成 `${__NEXT_NAME__}:${key}`,防止部署多套系统到同一域名不同目录时,变量共用的问题(`__NEXT_NAME__``package.json` 中的 `name`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 iframes 滚动条问题
- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
- 🎉 新增 工具类百分比验证演示
- 🐞 修复 [tag-view 标签右键会超出浏览器 #I4KN78](https://gitee.com/lyt-top/vue-next-admin/issues/I4KN78)
## 1.2.1
`2022.12.12`
`2021.12.12`
- 🐞 优化 版本升级提示
- 🐞 优化 深色模式
- 🌟 更新 依赖更新最新版本
- 🎯 优化 cropper 裁剪时卡顿问题 [#I4M2VQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4M2VQ)
- 🎯 优化 Wangeditor 富文本编辑器的问题 [#I4LPC1](https://gitee.com/lyt-top/vue-next-admin/issues/I4LPC1)、[#I4LM7I](https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I)
- 🐞 修复 浏览器标题问题
- 🐞 修复 element plus svg 图标引入
- 🐞 修复 工作流不可以拖线连接问题
## 1.2.0
`2022.12.06`
`2021.11.28`
- 🎉 新增 版本升级提示
- 🎉 新增 主题色修改
- 🎉 新增 深色模式
- 🐞 优化 外链界面 `/src/layout/routerView/link.vue`
- 🐞 修复 `菜单水平折叠` 刷新界面还原默认值问题
- 💔 移除 `vue.config.js` 打包加时间戳方法,因为打包报错了
- 🌟 更新 依赖更新最新版本
- 🎯 优化 深色模式
- 🎯 优化 `/@/utils` 文件夹,合并删除单一内容
- 🎯 优化 系统设置:菜单管理(新增、修改)、角色管理(新增菜单权限)、用户管理、部门管理、字典管理
- 🎯 优化 登录界面逻辑、权限管理逻辑
- 🎯 优化 同步 [vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 后端控制菜单模拟数据
- 🎉 新增 适配 Font Icon 向 SVG Icon 迁移(改动大,"element-plus": "^1.2.0-beta.4" 谨慎更新)
- 🐞 修复 热更新问题,感谢@甜蜜蜜
- 🐞 修复 页面/element 字体图标演示
- 🐞 修复 功能/图标选择器演示,新增高级功能 [issues #I4GJXQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4GJXQ)
## 1.1.2
`2021.10.17`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 开启全屏时,刷新界面被还原成未全屏的状态
- 🎯 优化 tagsView 右键菜单关闭逻辑
- 🎯 优化 wangeditor 富文本编辑器(增加双向绑定)
- 🎉 新增 工作流(暂不开源)
- 🎉 新增 基础版 ts不带国际化切换 `vue-next-admin-template` 分支
## 1.1.1
`2022.11.17`
`2021.09.25`
- 🐞 优化 [vue2 版本打包出来配置路由懒加载无效。](https://gitee.com/lyt-top/vue-next-admin/issues/I5RFQT),感谢[@林建生](https://gitee.com/ljsshuai)
- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
- 🎉 新增 工作流(未完成)
## 1.1.0
`2022.06.12`
`2021.09.10`
- 🐞 优化 部分界面演示图片不出来问题
- 🐞 修复 [vue-prev-admin 全屏模式下,滚动条无法滚到底 #I4S79C](https://gitee.com/lyt-top/vue-next-admin/issues/I4S79C),感谢[@qfvh](https://gitee.com/qfvh)
- 🌟 更新 依赖更新最新版本
- 🎯 优化 小屏模式下登录页二维码遮挡标题问题
- 🎉 新增 图片验证器
- 🎉 新增 动态复杂表单
- 🎉 新增 工作流(未完成)
- 🎉 新增 深色主题(伪深色,样式变动大,谨慎更新)
## 1.0.18
`2021.08.29`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 权限组件去掉顶级 div`/src/components/auth`
- 🎉 新增 布局配置添加恢复默认按钮
- 🐞 修复 升级 <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">element plus 1.1.0-beta.7</a>后项目无法启动、el-menu 菜单
- 🐞 修复 表格固定列时的层级、设置了相对定位时,遮挡左侧导航菜单问题
## 1.0.17
`2021.08.22`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 去除设置布局切换重置主题样式initSetLayoutChange切换布局需手动设置样式设置的样式自动同步各布局
- 🎯 优化 Dropdown 下拉菜单用户账号靠边时换行问题
- 🎯 优化 左侧导航菜单,共用菜单树,防止 `布局配置` 设置 `菜单 / 顶栏` 时,样式丢失等问题
- 🐞 修复 固定 header 后没有回到顶部的 bug拉取项目后运行不起来的 bug。<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/14" target="_blank">!14</a>,感谢<a href="https://gitee.com/wjs0509" target="_blank">@wjs0509</a>
- 🐞 修复 tagView 右键全屏后,浏览器窗口大小发生任何变化都会导致左边菜单显示出来,并且可点击打开对应页面。<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I46E6T" target="_blank">I46E6T</a>
- 🐞 修复 默认设置 `菜单 / 顶栏` 样式不生效问题(/@/src/store/modules/themeConfig.ts
## 1.0.16
`2021.08.14`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 菜单高亮(详情且详情设置了 meta.isHide 时,顶级菜单高亮),感谢群友@YourObject
- 🎯 优化 详情路径写法:如父级(/pages/filtering那么详情为/pages/filtering/details?id=1。这样写可实现详情时父级菜单高亮否则写成/pages/filteringDetails?id=1顶级菜单将不会高亮。可参考`页面/过滤筛选组件`,点击当前图片进行测试
- 🎯 优化 tagsView 右键菜单全屏时,打开的界面高度问题
- 🎯 优化 图表批量 resize 问题
- 🐞 修复 菜单收起时设置全局主题primary 且有二级菜单时),文字高亮颜色不对
- 🐞 修复 国际化 <a href="https://gitee.com/lyt-top/vue-next-admin/issues/I43NPE" target="_blank">#I43NPE</a>。可参考:`页面/过滤筛选组件`,点击顶部语言切换,进行底部分页国际化查看
## 1.0.15
`2021.08.06`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 tagsView 右键菜单点击时的字段名id 已修改成 contextMenuClickId与路由中返回的 id 名冲突问题,感谢群友@伯牙已遇钟子期
- 🎉 新增 多个 form 表单验证界面演示
## 1.0.14
`2021.07.29`
- 🌟 更新 依赖更新最新版本vue、vuex、vue-router,出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
- 🎯 优化 路由参数演示界面
- 🎯 优化 tagsView 操作演示界面由于存在相同路由多标签必须要传全部参数值query 或者 params
- 🎉 新增 开启 TagsView 共用开启时多个路由菜单共用一个详情组件参数为后点击的覆盖前面点击的tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
## 1.0.13
`2021.07.25`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2
- 🎉 新增 登录页扫码登录
## 1.0.12
`2021.07.16`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示空界面(待完善)
- 🎯 优化 tagsView 动态路由xxx/:id/:name时的右键菜单刷新、关闭其它时参数丢失问题2021.07.15 优化)
- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
## 1.0.11
`2021.07.14`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 路由参数、图片懒加载界面演示
- ⚠️ 警告 Form 表单 `binding value must be a string or number`,解决:加上 `label-position="top"` 不报警告(等待官方修复)
- 🎯 优化 锁屏界面动画效果、首页图表显示
- 🎯 优化 tagsView 右键菜单 `关闭` 功能逻辑
- 🐞 修复 开启 TagsView 拖拽报错及小于 `1000px` 时自动设置禁止拖拽(<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI" target="_blank">#I3ZRRI</a>
- 🐞 修复 `iframe 内嵌、外链` 高度问题,使用 computed 进行计算
- 🐞 修复 默认布局开启 `侧边栏 Logo` 与关闭 `菜单水平折叠`,切换到横向布局时,菜单看不见的问题
- 🐞 修复 切换不同布局时,再去开启 `经典布局分割菜单` 功能不生效问题
- 🐞 修复 浏览器窗口标题中/英文切换不实时生效的问题
- 🐞 修复 切换布局时,某些功能不可以使用。部分界面不需要取消事件监听(proxy.mittBus.off('xxx'))
- 🐞 修复 动态路由带参数router-link 跳转问题(<a href="hhttps://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G" target="_blank">#I3YX6G</a>
- 🐞 修复 横向菜单有二级菜单时,点击子级菜单不高亮问题
- 🐞 修复 功能 tagsView 操作演示不生效
## 1.0.10
`2021.07.07`
- 🌟 更新 依赖更新最新版本(字体图标无问题)
- 🎯 优化 内嵌 iframe、外链解决 tagsView 刷新问题
## 1.0.9
`2021.12.22`
`2021.07.02`
- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
- 🎉 新增 工具类百分比验证演示
- 🐞 修复 tag-view 标签右键会超出浏览器
- 🌟 更新 依赖更新最新版本
- 🎯 优化 图标选择器设置宽度、v-model 等问题
- 🎯 优化 滚动通知栏在手机上的体验
- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
- 🎯 优化 字体图标(自动载入) 逻辑
- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
## 1.0.8
`2021.12.16`
`2021.06.29`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 登录界面逻辑、权限管理逻辑
- 🎯 优化 同步 vue-next-admin-images 后端控制菜单模拟数据
- 🎯 优化 菜单格式(对象改数组)
- 🐞 修复 登录页手机端样式问题
- 🎉 新增 表单中英文切换演示
- 🎯 优化 登录页查看密码 icon 图标
- 🎯 优化 图标选择器
- 🎯 优化 拖动指令
- 🐞 修复 form 表单在页面小于 576px 时的排版问题
## 1.0.7
`2021.11.27`
`2021.06.24`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 登录问题 [#I4GIKU](https://gitee.com/lyt-top/vue-next-admin/issues/I4GIKU)
- 🎉 新增 拖动指令及其演示界面
- 🎯 优化 锁屏界面,解锁提示
- 🎯 优化 登录页在手机上显示的效果
## 1.0.6
`2021.11.08`
`2021.06.23`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 目录移动 `@/views/layout` 移动 `@/layout` (可全局替换)
- 🎯 优化 eslint 语法检测问题,`@babel/eslint-parser` 替换已废弃的 `babel-eslint`,出现报错,请尝试降级 eslint
- 🎯 优化 vuex 文件自动导入
- 🎯 优化 去掉内嵌 iframe 内边距padding
- 🎯 优化 城市多级联动组件
- 🎯 优化 Tree 树形控件改成表格组件
- 🐞 修复 Cascader 级联选择器高度问题
## 1.0.5
`2021.06.30`
`2021.06.22`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 表单自适应演示界面
- 🎯 优化 去掉内嵌 iframe 内边距padding
- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
- 🐞 修复 开启后端控制路由isRequestRoutes = true内嵌 iframe、外链不可使用的问题
## 1.0.4
`2021.06.19`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 项目仓库地址
- 🎯 优化 移除 utils/storage.ts 下的旧写法(改动较大)
- 🐞 修复 鼠标移入顶部用户信息栏 开/关全屏 文字反向问题
- 🌟 更新 依赖更新最新版本"vite": "^2.3.7")热更新无问题
- 🎉 新增 深克隆工具,方便开发,感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/6" target="_blank">#6</a>)
- 🎯 优化 vuex 模块自动导入。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/4" target="_blank">#4</a>),感谢群友@web 小学生-第五君
- 🎯 优化 类型定义提高编码体验修复不能将类型“string | undefined”分配给类型“string”的问题。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/5" target="_blank">#5</a>)
- 🎯 优化 `layout` 文件夹移动到与 `views` 文件夹同级(改动较大,`/@/views/layout` 变成 `/@/layout`
- 🎯 优化 页面有 `console.log``eslint` 不生效问题
- 🎯 优化 页面、ts 中 `any` 类型问题(改动较大)
- 🎯 优化 登录页在手机上显示的效果
- 🎯 优化 多行注释信息,鼠标放到方法名即可查看,更加直观的知道方法参数等。引入方法时需去掉以 `.ts` 结尾的后缀(改动较大)
- 🎯 优化 移除 `utils/storage.ts` 下的旧写法(改动较大)
- 🎯 优化 拆分 `router` 下内容,路由、前端、后端控制分开写,方便理解
- 🐞 修复 鼠标移入顶部用户信息栏 `开/关全屏` 文字反向问题
- 🐞 修复 热更新时NextLoading界面 loading 不消失问题 `window.nextLoading === undefined`
- 🐞 修复 vuex 中不可以使用 `/@/api/xxx` 下的接口调用问题
## 1.0.3
`2021.06.02`
- 🌟 更新 依赖更新最新版本
- 💯 优化 动态加载后端返回路由路由(模拟数据)方法
- ❄️ 删除 G6 思维导图界面
- 🌟 更新 手动更新 vue、vue-router、vuex 到最近最多人使用的版本,出现不可预测的问题请降低版本。版本查看:<a href="https://www.npmjs.com/package/vue" target="_blank">vue 版本查看</a>
- 🐞 修复 开启后端控制路由 `isRequestRoutes` 在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期
## 1.0.2
`2021.06.01`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意
## 1.0.1
`2021.05.31`
- 🎉 新增 新增个人中心演示空白页
- 🎉 新增 更新日志文件 `CHANGELOG.md`,以后每次更新都会在这里显示对应内容
- 🌟 更新 依赖更新最新版本
- 🐞 修复 分栏布局路由设置 `meta.isHide``true` 时报错问题,感谢群友@29@芭芭拉
- 🐞 修复 分栏、经典布局路由设置 `meta.isHide``true` 时报错问题,感谢群友@29@芭芭拉
- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题

40
LICENSE
View File

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2021 lyt-Top
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
MIT License
Copyright (c) 2021 lyt-Top
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,37 +1,33 @@
<div align="center">
<img src="https://i.hd-r.cn/07fd893a28d4f62926fe6ad895af96a2.png">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/logo/logo-text.svg">
<p align="center">
<a href="https://cn.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue2.x-green" alt="vue">
<a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
</a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus">
</a>
<a href="https://www.tslang.cn/" target="_blank">
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a>
<a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a>
<a href="https://element.eleme.cn/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--ui-%3E1.0.0-blue" alt="element ui">
</a>
<a href="https://v4.webpack.docschina.org/concepts/" target="_blank">
<img src="https://img.shields.io/badge/webpack-%3E1.0.0-success" alt="webpack">
</a>
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/vue-prev-admin/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-yellow" alt="license">
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
</a>
</p>
<p>&nbsp;</p>
</div>
#### 💝 长期赞助商
<a href="http://www.ccflow.org/" target="_blank">
<img src="./src/assets/ccflowRightNextAdmin.png" width="50%" height="70px">
</a>
#### 🌈 介绍
基于 vue2.x + webpack + element ui适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### ⛱️ 线上预览
- vue3.x 版本预览vue-next-admin<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-next-admin-preview/#/login</a>
- vue2.x 版本预览vue-prev-admin<a href="https://lyt-top.gitee.io/vue-prev-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-prev-admin-preview/#/login</a>
- vue3.x + uni-app 商城 H5vue-next-admin-shop<a href="https://lyt-top.gitee.io/vue-next-admin-shop-preview" target="_blank">https://lyt-top.gitee.io/vue-next-admin-shop-preview</a>
#### 💒 代码仓库
@ -43,11 +39,17 @@
- 复制代码(桌面 cmd 运行) `npm install -g cnpm --registry=https://registry.npm.taobao.org`
- 复制代码(桌面 cmd 运行) `npm install -g yarn`
#### 🏭 环境支持
| Edge | last 2 versions | last 2 versions | last 2 versions |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| ![Edge](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
#### ⚡ 使用说明
建议使用 cnpm因为 yarn 有时会报错。`npm install` 安装报错的话,请使用 `cnpm install`
> 注意:`node` 需大于 `12.xxx` 小于等于 `v16.14.0`,否则安装依赖将报错。
建议使用 cnpm因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a>
```bash
# 克隆项目
@ -56,9 +58,6 @@ git clone https://gitee.com/lyt-top/vue-next-admin.git
# 进入项目
cd vue-next-admin
# 切换分支
git checkout vue-prev-admin
# 安装依赖
cnpm install
@ -69,29 +68,25 @@ cnpm run dev
cnpm run build
```
#### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
#### 💯 学习交流加 QQ 群
> 1 - 4 交流群已满,请加 vue-next-admin 交流群 5
- 若加群了没同意(一般不会超过一天),那就是群满了,请换一个群试试
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
- 群号码:
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>
群号556254895
其它交流群请查看文档首页 [vueNextAdmin 解疑问](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq1.png" width="220" height="220" alt="vue-next-admin 讨论群" title="vue-next-admin 讨论群1"/>
</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq2.png" width="220" height="220" alt="vue-next-admin 讨论群" title="vue-next-admin 讨论群2"/>
</a>
#### 💒 集成后端
- <a target="_blank" href="https://gitee.com/zuohuaijun/Admin.NET">@zuohuaijun Admin.NET</a>
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
- <a target="_blank" href="https://toscode.gitee.com/GionConnection/gopro_free">@甜蜜蜜 GoPro 平台</a>
- <a target="_blank" href="https://gitee.com/GionConnection/niupi-free">@甜蜜蜜 NiuPi 平台</a>
- <a target="_blank" href="https://gitee.com/tiger1103/gfast/tree/os-v3/">@游子 GFast-V3</a>
- <a target="_blank" href="https://gitee.com/diygw/diygw-ui-php/">@diygw.com gw-ui-php</a>
- <a target="_blank" href="https://gitee.com/zsvg/vboot-net">@zsvg vboot-net</a>
- <a target="_blank" href="https://gitee.com/zsvg/vboot-java">@zsvg vboot-java</a>
- <a target="_blank" href="https://gitee.com/wonderful-code/buildadmin">@青红造了个白 buildadmin</a>
- <a target="_blank" href="https://www.gnet.top/public">@甜蜜蜜 GoPro 平台</a>
#### ❤️ 鸣谢列表
@ -99,24 +94,36 @@ cnpm run build
- <a href="https://github.com/vuejs/vue-next" target="_blank">vue-next</a>
- <a href="https://github.com/ElemeFE/element" target="_blank">element-ui</a>
- <a href="https://github.com/element-plus/element-plus" target="_blank">element-plus</a>
- <a href="https://github.com/vuejs/vue-router-next" target="_blank">vue-router-nex</a>
- <a href="https://github.com/vuejs/vue-router-next" target="_blank">vue-router-next</a>
- <a href="https://github.com/vuejs/vuex" target="_blank">vuex</a>
- <a href="https://github.com/apache/echarts" target="_blank">echarts</a>
- <a href="https://github.com/axios/axios" target="_blank">axios</a>
- <a href="https://github.com/zenorocha/clipboard.js" target="_blank">clipboard</a>
- <a href="https://github.com/inorganik/countUp.js" target="_blank">countUp</a>
- <a href="https://github.com/developit/mitt" target="_blank">mitt</a>
- <a href="https://github.com/rstacruz/nprogress" target="_blank">nprogress</a>
- <a href="https://github.com/sindresorhus/screenfull.js" target="_blank">screenfull</a>
- <a href="https://github.com/SortableJS/Sortable" target="_blank">sortablejs</a>
- <a href="https://github.com/sass/sass" target="_blank">sass</a>
- <a href="https://github.com/microsoft/TypeScript" target="_blank">typescript</a>
- <a href="https://github.com/vitejs/vite" target="_blank">vite</a>
- <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a>
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs</a>
- <a href="https://github.com/crabbly/Print.js" target="_blank">print-js</a>
- <a href="https://github.com/likaia/screen-shot" target="_blank">vue-web-screen-shot</a>
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</a>
- <a href="https://github.com/antoniandre/splitpanes" target="_blank">splitpanes</a>
- <a href="https://github.com/yimijianfang/vue-drag-verify" target="_blank">vue-drag-verify</a>
- <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a>
#### 💕 特别感谢
特别感谢群里老哥的建议、指导与帮忙谢谢!
特别感谢老哥的建议、指导与帮忙谢谢!
- <a href="https://gitee.com/click33/sa-plus" target="_blank">@省长</a>
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参</a>
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参
- <a href="https://gitee.com/chuange" target="_blank">@川歌</a>
- @华仔
#### 💌 支持作者

View File

@ -1,4 +0,0 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
plugins: ['@babel/plugin-proposal-optional-chaining'],
};

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
@ -12,19 +12,21 @@
name="description"
content="vue-next-admin基于 vue3 + CompositionAPI + typescript + vite + element plus适配手机、平板、pc 的后台开源免费管理系统模板vue-prev-admin基于 vue2 + element ui适配手机、平板、pc 的后台开源免费管理系统模板!"
/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>vue-prev-admin</title>
<link rel="icon" href="/favicon.ico" />
<title>vue-next-admin</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
var _hmt = _hmt || [];
(function() {
(function () {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?9d1e524198ede8205ac7c938c243344c';
hm.src = 'https://hm.baidu.com/hm.js?d9c8b87d10717013641458b300c552e4';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="module" src="/src/main.ts"></script>
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
</body>
</html>

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"/@/*": ["src/*"]
}
}
}

7904
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +1,59 @@
{
"name": "vue-prev-admin",
"version": "1.2.3",
"private": true,
"description": "vue2 webpack admin template",
"name": "vue-next-admin",
"version": "1.2.2",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"webpack": "webpack --version"
"dev": "vite --force",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"axios": "0.24.0",
"clipboard": "2.0.8",
"countup.js": "2.0.8",
"echarts": "5.2.2",
"element-ui": "2.15.6",
"nprogress": "0.2.0",
"screenfull": "5.2.0",
"sign-canvas": "1.1.4",
"vue": "2.6.14",
"vue-i18n": "8.26.7",
"vue-particles": "1.0.9",
"vue-router": "3.5.3",
"vue-seamless-scroll": "1.1.23",
"vuex": "3.6.2"
"@element-plus/icons-vue": "^0.2.4",
"axios": "^0.24.0",
"countup.js": "^2.0.8",
"cropperjs": "^1.5.12",
"echarts": "^5.2.2",
"echarts-gl": "^2.0.8",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^1.2.0-beta.6",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"splitpanes": "^3.0.4",
"vue": "^3.2.20",
"vue-clipboard3": "^1.0.1",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12",
"vue-web-screen-shot": "^1.3.2",
"vuex": "^4.0.2",
"wangeditor": "^4.7.10"
},
"devDependencies": {
"@babel/eslint-parser": "7.16.5",
"@babel/plugin-proposal-optional-chaining": "7.17.12",
"@vue/cli-plugin-babel": "4.5.15",
"@vue/cli-plugin-eslint": "4.5.15",
"@vue/cli-plugin-router": "4.5.15",
"@vue/cli-plugin-vuex": "4.5.15",
"@vue/cli-service": "4.5.15",
"eslint": "8.4.1",
"eslint-plugin-vue": "8.2.0",
"sass": "1.45.0",
"sass-loader": "10.1.1",
"vue-template-compiler": "2.6.14"
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^17.0.2",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"@vitejs/plugin-vue": "^2.0.1",
"@vue/compiler-sfc": "^3.2.26",
"dotenv": "^10.0.0",
"eslint": "^8.5.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.5.1",
"sass": "^1.45.1",
"sass-loader": "^12.4.0",
"typescript": "^4.5.4",
"vite": "^2.7.4",
"vue-eslint-parser": "^8.0.1"
},
"browserslist": [
"> 1%",

1
plugins.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'vue-grid-layout';

View File

@ -1,290 +0,0 @@
{
"code": 0,
"data": [
{
"path": "/home",
"name": "home",
"component": "home",
"meta": {
"title": "message.router.home",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": true,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-shouye"
}
},
{
"path": "/tools",
"name": "tools",
"component": "tools",
"meta": {
"title": "message.router.tools",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-gongju"
}
},
{
"path": "/menu",
"name": "menu",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1",
"meta": {
"title": "message.router.menu",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1",
"name": "menu1",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu11",
"meta": {
"title": "message.router.menu1",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu11",
"name": "menu11",
"component": "menu/menu1/menu11/index",
"meta": {
"title": "message.router.menu11",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12",
"name": "menu12",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu12/menu121",
"meta": {
"title": "message.router.menu12",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu12/menu121",
"name": "menu121",
"component": "menu/menu1/menu12/menu121/index",
"meta": {
"title": "message.router.menu121",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12/menu122",
"name": "menu122",
"component": "menu/menu1/menu12/menu122/index",
"meta": {
"title": "message.router.menu122",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu1/menu13",
"name": "menu13",
"component": "menu/menu1/menu13/index",
"meta": {
"title": "message.router.menu13",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu2",
"name": "menu2",
"component": "menu/menu2/index",
"meta": {
"title": "message.router.menu2",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/fun",
"name": "funIndex",
"component": "layout/routerView/parent",
"redirect": "/fun/tagsView",
"meta": {
"title": "message.router.funIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-crew_feature"
},
"children": [
{
"path": "/fun/tagsView",
"name": "funTagsView",
"component": "fun/tagsView/index",
"meta": {
"title": "message.router.funTagsView",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-thumb"
}
},
{
"path": "/fun/signCanvas",
"name": "funSignCanvas",
"component": "fun/signCanvas/index",
"meta": {
"title": "message.router.funSignCanvas",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-edit"
}
}
]
},
{
"path": "/pages",
"name": "pagesIndex",
"component": "layout/routerView/parent",
"redirect": "/pages/formAdapt",
"meta": {
"title": "message.router.pagesIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-fuzhiyemian"
},
"children": [
{
"path": "/pages/formAdapt",
"name": "pagesFormAdapt",
"component": "pages/formAdapt/index",
"meta": {
"title": "message.router.pagesFormAdapt",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-biaodan"
}
}
]
},
{
"path": "/personal",
"name": "personal",
"component": "personal/index",
"meta": {
"title": "message.router.personal",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-gerenzhongxin"
}
},
{
"path": "/link",
"name": "layoutLinkView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutLinkView",
"isLink": "https://element-plus.gitee.io/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caozuo-wailian"
}
},
{
"path": "/iframes",
"name": "layoutIfameView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutIfameView",
"isLink": "https://element-plus.gitee.io/zh-CN/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": true,
"isIframe": true,
"roles": ["admin"],
"icon": "iconfont icon-neiqianshujuchucun"
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,227 +0,0 @@
{
"code": 0,
"data": [
{
"path": "/home",
"name": "home",
"component": "home",
"meta": {
"title": "message.router.home",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": true,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-shouye"
}
},
{
"path": "/tools",
"name": "tools",
"component": "tools",
"meta": {
"title": "message.router.tools",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-gongju"
}
},
{
"path": "/menu",
"name": "menu",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1",
"meta": {
"title": "message.router.menu",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1",
"name": "menu1",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu11",
"meta": {
"title": "message.router.menu1",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu11",
"name": "menu11",
"component": "menu/menu1/menu11/index",
"meta": {
"title": "message.router.menu11",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12",
"name": "menu12",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu12/menu121",
"meta": {
"title": "message.router.menu12",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu12/menu121",
"name": "menu121",
"component": "menu/menu1/menu12/menu121/index",
"meta": {
"title": "message.router.menu121",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12/menu122",
"name": "menu122",
"component": "menu/menu1/menu12/menu122/index",
"meta": {
"title": "message.router.menu122",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu1/menu13",
"name": "menu13",
"component": "menu/menu1/menu13/index",
"meta": {
"title": "message.router.menu13",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu2",
"name": "menu2",
"component": "menu/menu2/index",
"meta": {
"title": "message.router.menu2",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/fun",
"name": "funIndex",
"component": "layout/routerView/parent",
"redirect": "/fun/tagsView",
"meta": {
"title": "message.router.funIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-crew_feature"
},
"children": [
{
"path": "/fun/tagsView",
"name": "funTagsView",
"component": "fun/tagsView/index",
"meta": {
"title": "message.router.funTagsView",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-thumb"
}
}
]
},
{
"path": "/link",
"name": "layoutLinkView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutLinkView",
"isLink": "https://element-plus.gitee.io/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caozuo-wailian"
}
},
{
"path": "/iframes",
"name": "layoutIfameView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutIfameView",
"isLink": "https://gitee.com/lyt-top/vue-next-admin",
"isHide": false,
"isKeepAlive": false,
"isAffix": true,
"isIframe": true,
"roles": ["admin"],
"icon": "iconfont icon-neiqianshujuchucun"
}
}
]
}

13
shim.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
/* eslint-disable */
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx使用 getCurrentInstance() 来获取
interface Window {
nextLoading: boolean;
}

6
source.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';

View File

@ -1,78 +1,87 @@
<template>
<div id="app">
<router-view />
<Setings ref="setingsRef" />
<Upgrade v-if="getVersion" />
<Sponsors />
</div>
<el-config-provider :locale="i18nLocale">
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
<LockScreen v-if="getThemeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
<CloseFull />
</el-config-provider>
</template>
<script>
import config from '/package.json';
import setIntroduction from '@/utils/setIconfont.js';
import { Local } from '@/utils/storage.js';
import Setings from '@/layout/navBars/topBar/setings.vue';
import Upgrade from '@/layout/upgrade/index.vue';
import Sponsors from '@/layout/sponsors/index.vue';
export default {
name: 'App',
components: { Setings, Upgrade, Sponsors },
mounted() {
this.initSetIconfont();
this.openSetingsDrawer();
this.getLayoutThemeConfig();
},
computed: {
// 获取版本号
getVersion() {
let isVersion = false;
if (this.$route.path !== '/login') {
if ((Local.get('version') && Local.get('version') !== config.version) || !Local.get('version')) isVersion = true;
}
return isVersion;
},
},
methods: {
<script lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
export default defineComponent({
name: 'app',
components: { LockScreen, Setings, CloseFull },
setup() {
const { proxy } = getCurrentInstance() as any;
const setingsRef = ref();
const route = useRoute();
const store = useStore();
const state = reactive({
i18nLocale: null,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 设置初始化,防止刷新时恢复默认
initSetIconfont() {
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
},
// 布局配置弹窗打开
openSetingsDrawer() {
this.bus.$on('openSetingsDrawer', () => {
this.$refs.setingsRef.openDrawer();
});
},
// 获取缓存中的布局配置
getLayoutThemeConfig() {
if (Local.get('themeConfigPrev')) {
this.$store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfigPrev'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
} else {
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
}
},
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.$nextTick(() => {
let webTitle = '';
let { globalTitle } = this.$store.state.themeConfig.themeConfig;
to.path === '/login' ? (webTitle = to.meta.title) : (webTitle = this.$t(to.meta.title));
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
},
deep: true,
immediate: true,
},
// 设置 i18nApp.vue 中的 el-config-provider
proxy.mittBus.on('getI18nConfig', (locale: string) => {
state.i18nLocale = locale;
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
}
});
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
proxy.mittBus.off('openSetingsDrawer', () => {});
proxy.mittBus.off('getI18nConfig', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
}
);
return {
setingsRef,
getThemeConfig,
...toRefs(state),
};
},
destroyed() {
this.bus.$off('openSetingsDrawer');
},
};
});
</script>

View File

@ -1,25 +0,0 @@
import request from '@/utils/request';
/**
* 登录api接口集合
* @method signIn 用户登录
* @method signOut 用户退出登录
*/
export function useLoginApi() {
return {
signIn: (params) => {
return request({
url: '/user/signIn',
method: 'post',
data: params,
});
},
signOut: (params) => {
return request({
url: '/user/signOut',
method: 'post',
data: params,
});
},
};
}

27
src/api/login/index.ts Normal file
View File

@ -0,0 +1,27 @@
import request from '/@/utils/request';
/**
* 用户登录
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signIn(params: object) {
return request({
url: '/user/signIn',
method: 'post',
data: params,
});
}
/**
* 用户退出登录
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signOut(params: object) {
return request({
url: '/user/signOut',
method: 'post',
data: params,
});
}

View File

@ -1,48 +0,0 @@
import request from '@/utils/request';
/**
* webpack 的代理只是本地开发生效,打包后需要在部署环境 搭建 nginx 代理
* 所以:
* 开发环境,请求跨越的接口。为了配置跨越示例
* 线上环境,请求目录下的 `json` 数据
* 一般后端接口都会处理跨越问题,可根据具体情况进行修改
* json 格式地址https://gitee.com/lyt-top/vue-next-admin-images/tree/master/vue2
* 本地菜单地址public/xxx.json
*/
/**
* 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由isRequestRoutes 为 true则开启后端控制路由
* @method getMenuAdmin 获取后端动态路由菜单(admin)
* @method getMenuTest 获取后端动态路由菜单(test)
*/
export function useMenuApi() {
return {
getMenuAdmin: (params) => {
// 本地数据,路径:`/public/xxx.json`
return request({
url: './admin.json',
method: 'get',
params,
});
// 模拟跨域
// return request({
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/vue2/admin.json',
// method: 'get',
// });
},
getMenuTest: (params) => {
// 本地数据,路径:`/public/xxx.json`
return request({
url: './test.json',
method: 'get',
params,
});
// 模拟跨域
// return request({
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/vue2/test.json',
// method: 'get',
// });
},
};
}

34
src/api/menu/index.ts Normal file
View File

@ -0,0 +1,34 @@
import request from '/@/utils/request';
/**
* 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由isRequestRoutes 为 true则开启后端控制路由
*/
/**
* 获取后端动态路由菜单(admin)
* @link 参考https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* @param params 要传的参数值,非必传
* @returns 返回接口数据
*/
export function getMenuAdmin(params?: object) {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
method: 'get',
params,
});
}
/**
* 获取后端动态路由菜单(test)
* @link 参考https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* @param params 要传的参数值,非必传
* @returns 返回接口数据
*/
export function getMenuTest(params?: object) {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
method: 'get',
params,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 50" width="64" height="50">
<style>
tspan { white-space:pre }
</style>
<path id="p " fill="#409eff" d="M32.37 1.82L32.37 1.82Q33.13 1.88 33.13 2.05L33.77 1.94Q44.03 1.94 49.24 11.55Q50.12 13.71 50.12 15.94Q50.12 20.75 45.61 24.03Q41.86 26.31 37.93 26.31L37.82 26.31Q34.24 26.31 30.02 25.14Q28.32 30.71 25.86 40.61L26.04 40.84Q25.39 43.36 24.46 48.4Q24.34 48.4 24.05 48.52Q19.13 46.23 19.13 42.84Q20.18 36.45 20.71 34.75Q20.71 31.82 16.31 25.67Q14.73 22.21 14.73 20.1L14.85 19.87Q14.85 19.75 14.73 19.75Q15.67 14.54 16.31 14.54Q17.07 11.96 22.64 6.63Q25.45 4.46 28.85 2.99Q30.67 2.52 32.37 1.82ZM35.77 4.69L35.77 4.69L35.77 4.81Q35.77 7.09 31.72 19.05Q31.72 19.4 30.67 22.56L30.55 23.21L30.55 23.62Q30.9 24.14 34.18 24.14Q38.23 24.14 42.09 21.57Q45.43 19.11 46.37 15.71L46.25 15.41Q46.66 14.54 46.66 14.01L46.66 12.31Q46.66 10.02 43.91 6.74Q40.8 4.69 36.53 4.69L35.77 4.69ZM18.48 16.88L18.48 16.88Q21.88 20.22 24.05 21.27Q24.81 19.52 26.04 15.18L27.45 10.79L28.73 7.27Q29.03 7.27 29.03 6.51L28.85 6.51Q22.64 8.97 18.77 16.12Q18.77 16.18 18.48 16.88ZM18.13 17.41L18.13 17.41Q17.6 19.05 17.31 21.57Q17.31 25.9 21.59 31L23.93 22.33Q23.93 22.04 21.06 20.1Q18.48 17.76 18.48 17.41L18.13 17.41Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,27 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'auth',
props: {
value: {
type: String,
default: () => '',
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
return store.state.userInfos.userInfos.authBtnList.some((v: any) => v === props.value);
});
return {
getUserAuthBtnList,
};
},
};
</script>

View File

@ -0,0 +1,28 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';
export default {
name: 'authAll',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
});
return {
getUserAuthBtnList,
};
},
};
</script>

View File

@ -0,0 +1,33 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'auths',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const store = useStore();
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: any) => {
props.value.map((v) => {
if (val === v) flag = true;
});
});
return flag;
});
return {
getUserAuthBtnList,
};
},
};
</script>

View File

@ -0,0 +1,148 @@
<template>
<div>
<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="cropperImg" class="cropper-warp-left-img" />
</div>
<div class="cropper-warp-right">
<div class="cropper-warp-right-title">预览</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img" />
</div>
<div class="cropper-warp-right-label">100 x 100</div>
</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, nextTick } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
export default {
name: 'cropperIndex',
setup() {
const state = reactive({
isShowDialog: false,
cropperImg: '',
cropperImgBase64: '',
cropper: null,
});
// 打开弹窗
const openDialog = (imgs: any) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
initCropper();
});
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 更换
const onSubmit = () => {
// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
};
// 初始化cropperjs图片裁剪
const initCropper = () => {
const letImg: any = document.querySelector('.cropper-warp-left-img');
state.cropper = new Cropper(letImg, {
viewMode: 1,
dragMode: 'none',
initialAspectRatio: 1,
aspectRatio: 1,
preview: '.before',
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
crop: () => {
state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
},
});
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
initCropper,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: var(--el-border-base);
background: var(--color-whites);
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: var(--el-border-radius-base);
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 350px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
display: flex;
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: var(--el-border-radius-circle);
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: var(--el-text-color-primary);
height: 30px;
line-height: 30px;
}
}
}
}
</style>

View File

@ -0,0 +1,296 @@
<template>
<div
ref="dragVerify"
class="drag_verify"
:style="dragVerifyStyle"
@mousemove="dragMoving"
@mouseup="dragFinish"
@mouseleave="dragFinish"
@touchmove="dragMoving"
@touchend="dragFinish"
>
<div class="dv_progress_bar" :class="{ goFirst2: isOk }" ref="progressBar" :style="progressBarStyle"></div>
<div class="dv_text" :style="textStyle" ref="message">
<slot name="textBefore" v-if="$slots.textBefore"></slot>
{{ message }}
<slot name="textAfter" v-if="$slots.textAfter"></slot>
</div>
<div
class="dv_handler dv_handler_bg"
:class="{ goFirst: isOk }"
@mousedown="dragStart"
@touchstart="dragStart"
ref="handler"
:style="handlerStyle"
>
<i :class="handlerIcon"></i>
</div>
</div>
</template>
<script>
export default {
name: 'dragVerify',
props: {
isPassing: {
type: Boolean,
default: false,
},
width: {
type: Number,
default: 250,
},
height: {
type: Number,
default: 40,
},
text: {
type: String,
default: 'swiping to the right side',
},
successText: {
type: String,
default: 'success',
},
background: {
type: String,
default: '#eee',
},
progressBarBg: {
type: String,
default: '#76c61d',
},
completedBg: {
type: String,
default: '#76c61d',
},
circle: {
type: Boolean,
default: false,
},
radius: {
type: String,
default: '4px',
},
handlerIcon: {
type: String,
},
successIcon: {
type: String,
},
handlerBg: {
type: String,
default: '#fff',
},
textSize: {
type: String,
default: '14px',
},
textColor: {
type: String,
default: '#333',
},
},
mounted: function () {
const dragEl = this.$refs.dragVerify;
dragEl.style.setProperty('--textColor', this.textColor);
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px');
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px');
console.log(this.$slots);
},
computed: {
handlerStyle: function () {
return {
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg,
};
},
message: function () {
return this.isPassing ? this.successText : this.text;
},
dragVerifyStyle: function () {
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius,
};
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px' : this.radius,
};
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize,
};
},
},
data() {
return {
isMoving: false,
x: 0,
isOk: false,
};
},
methods: {
dragStart: function (e) {
if (!this.isPassing) {
this.isMoving = true;
this.x = e.pageX || e.touches[0].pageX;
}
this.$emit('handlerMove');
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x;
var handler = this.$refs.handler;
if (_x > 0 && _x <= this.width - this.height) {
handler.style.left = _x + 'px';
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px';
} else if (_x > this.width - this.height) {
handler.style.left = this.width - this.height + 'px';
this.$refs.progressBar.style.width = this.width - this.height / 2 + 'px';
this.passVerify();
}
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.changedTouches[0].pageX) - this.x;
if (_x < this.width - this.height) {
this.isOk = true;
var that = this;
setTimeout(function () {
that.$refs.handler.style.left = '0';
that.$refs.progressBar.style.width = '0';
that.isOk = false;
}, 500);
this.$emit('passfail');
} else {
var handler = this.$refs.handler;
handler.style.left = this.width - this.height + 'px';
this.$refs.progressBar.style.width = this.width - this.height / 2 + 'px';
this.passVerify();
}
this.isMoving = false;
}
},
passVerify: function () {
this.$emit('update:isPassing', true);
this.isMoving = false;
var handler = this.$refs.handler;
handler.children[0].className = this.successIcon;
this.$refs.progressBar.style.background = this.completedBg;
this.$refs.message.style['-webkit-text-fill-color'] = 'unset';
this.$refs.message.style.animation = 'slidetounlock2 3s infinite';
this.$refs.message.style.color = '#fff';
this.$emit('passcallback');
},
reset: function () {
const oriData = this.$options.data();
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this[key] = oriData[key];
}
}
var handler = this.$refs.handler;
var message = this.$refs.message;
handler.style.left = '0';
this.$refs.progressBar.style.width = '0';
handler.children[0].className = this.handlerIcon;
message.style['-webkit-text-fill-color'] = 'transparent';
message.style.animation = 'slidetounlock 3s infinite';
message.style.color = this.background;
},
},
};
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
</style>
<style>
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@ -0,0 +1,453 @@
<template>
<div class="drag-verify-container">
<div :style="dragVerifyImgStyle">
<img ref="checkImg" :src="imgsrc" @load="checkimgLoaded" style="width: 100%" alt="" />
<div class="move-bar" :class="{ goFirst: isOk, goKeep: isKeep }" :style="movebarStyle" ref="moveBar" v-show="showBar"></div>
<div class="clip-bar" :style="clipbarStyle" ref="clipBar"></div>
<div class="refresh" v-if="showRefresh && !isPassing">
<i :class="refreshIcon" @click="refreshimg"></i>
</div>
<div class="tips success" v-if="showTips && isPassing">{{ successTip }}</div>
<div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{ failTip }}</div>
</div>
<div
ref="dragVerify"
class="drag_verify"
:style="dragVerifyStyle"
@mousemove="dragMoving"
@mouseup="dragFinish"
@mouseleave="dragFinish"
@touchmove="dragMoving"
@touchend="dragFinish"
>
<div class="dv_progress_bar" :class="{ goFirst2: isOk }" ref="progressBar" :style="progressBarStyle">
{{ successMessage }}
</div>
<div class="dv_text" :style="textStyle" ref="message">
{{ message }}
</div>
<div
class="dv_handler dv_handler_bg"
:class="{ goFirst: isOk }"
@mousedown="dragStart"
@touchstart="dragStart"
ref="handler"
:style="handlerStyle"
>
<i :class="handlerIcon"></i>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dragVerify',
props: {
isPassing: {
type: Boolean,
default: false,
},
width: {
type: Number,
default: 250,
},
height: {
type: Number,
default: 40,
},
text: {
type: String,
default: 'swiping to the right side',
},
successText: {
type: String,
default: 'success',
},
background: {
type: String,
default: '#eee',
},
progressBarBg: {
type: String,
default: '#76c61d',
},
completedBg: {
type: String,
default: '#76c61d',
},
circle: {
type: Boolean,
default: false,
},
radius: {
type: String,
default: '4px',
},
handlerIcon: {
type: String,
},
successIcon: {
type: String,
},
handlerBg: {
type: String,
default: '#fff',
},
textSize: {
type: String,
default: '14px',
},
textColor: {
type: String,
default: '#333',
},
imgsrc: {
type: String,
},
barWidth: {
type: Number,
default: 70,
},
barHeight: {
type: Number,
default: 40,
},
barRadius: {
type: Number,
default: 2,
},
showRefresh: {
type: Boolean,
default: false,
},
refreshIcon: {
type: String,
},
showTips: {
type: Boolean,
default: true,
},
successTip: {
type: String,
default: '验证通过超过80%用户',
},
failTip: {
type: String,
default: '验证未通过,拖动滑块将悬浮图像正确合并',
},
diffWidth: {
type: Number,
default: 20,
},
},
mounted: function () {
const dragEl = this.$refs.dragVerify;
dragEl.style.setProperty('--textColor', this.textColor);
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px');
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px');
},
computed: {
handlerStyle: function () {
return {
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg,
};
},
message: function () {
return this.isPassing ? '' : this.text;
},
successMessage: function () {
return this.isPassing ? this.successText : '';
},
dragVerifyStyle: function () {
console.log(this.width, 'width');
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius,
};
},
dragVerifyImgStyle: function () {
return {
width: this.width + 'px',
position: 'relative',
overflow: 'hidden',
};
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px' : this.radius,
};
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize,
};
},
},
data() {
return {
isMoving: false,
x: 0,
isOk: false,
isKeep: false,
movebarStyle: {},
clipbarStyle: {},
showBar: false,
clipBarx: 0,
showErrorTip: false,
};
},
methods: {
checkimgLoaded: function () {
//生成图片缺失位置
var barWidth = this.barWidth;
var barHeight = this.barHeight;
var imgHeight = this.$refs.checkImg.height;
var halfWidth = Math.floor(this.width / 2);
var refreshHeigth = 25;
var tipHeight = 20;
var x = halfWidth + Math.ceil(Math.random() * (halfWidth - barWidth));
var y = refreshHeigth + Math.floor(Math.random() * (imgHeight - barHeight - refreshHeigth - tipHeight));
this.clipbarStyle = {
width: barWidth + 'px',
height: barHeight + 'px',
top: y + 'px',
left: x + 'px',
'border-radius': this.barRadius + 'px',
};
this.clipBarx = x;
var imgsrc = this.imgsrc;
var width = this.width;
this.movebarStyle = {
background: `url(${imgsrc})`,
'background-position': `-${x}px -${y}px`,
'background-size': `${width}px`,
width: barWidth + 'px',
height: barHeight + 'px',
top: y + 'px',
'border-radius': this.barRadius + 'px',
};
},
dragStart: function (e) {
if (!this.isPassing) {
this.isMoving = true;
this.x = e.pageX || e.touches[0].pageX;
}
this.showBar = true;
this.showErrorTip = false;
this.$emit('handlerMove');
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x;
var handler = this.$refs.handler;
handler.style.left = _x + 'px';
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px';
this.$refs.moveBar.style.left = _x + 'px';
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.changedTouches[0].pageX) - this.x;
if (Math.abs(_x - this.clipBarx) > this.diffWidth) {
this.isOk = true;
var that = this;
setTimeout(function () {
that.$refs.handler.style.left = '0';
that.$refs.progressBar.style.width = '0';
that.$refs.moveBar.style.left = '0';
that.isOk = false;
}, 500);
this.showErrorTip = true;
this.$emit('passfail');
} else {
this.passVerify();
}
this.isMoving = false;
}
},
passVerify: function () {
this.$emit('update:isPassing', true);
this.isMoving = false;
var handler = this.$refs.handler;
handler.children[0].className = this.successIcon;
this.$refs.progressBar.style.background = this.completedBg;
this.$refs.message.style['-webkit-text-fill-color'] = 'unset';
this.$refs.message.style.animation = 'slidetounlock2 3s infinite';
this.$refs.progressBar.style.color = '#fff';
this.$refs.progressBar.style.fontSize = this.textSize;
this.isKeep = true;
setTimeout(() => {
this.$refs.moveBar.style.left = this.clipBarx + 'px';
setTimeout(() => {
this.isKeep = false;
}, 200);
}, 100);
this.$emit('passcallback');
},
reset: function () {
this.reImg();
this.checkimgLoaded();
},
reImg: function () {
this.$emit('update:isPassing', false);
const oriData = this.$options.data();
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this[key] = oriData[key];
}
}
var handler = this.$refs.handler;
var message = this.$refs.message;
handler.style.left = '0';
this.$refs.progressBar.style.width = '0';
handler.children[0].className = this.handlerIcon;
message.style['-webkit-text-fill-color'] = 'transparent';
message.style.animation = 'slidetounlock 3s infinite';
message.style.color = this.background;
},
refreshimg: function () {
this.$emit('refresh');
},
},
watch: {
imgsrc: {
immediate: false,
handler: function () {
this.reImg();
},
},
},
};
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goKeep {
transition: left 0.2s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
.drag-verify-container {
position: relative;
line-height: 0;
}
.move-bar {
position: absolute;
z-index: 100;
}
.clip-bar {
position: absolute;
background: rgba(255, 255, 255, 0.8);
}
.refresh {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
font-size: 20px;
z-index: 200;
}
.tips {
position: absolute;
bottom: 0;
height: 20px;
line-height: 20px;
text-align: center;
width: 100%;
font-size: 12px;
z-index: 200;
}
.tips.success {
background: rgba(255, 255, 255, 0.6);
color: green;
}
.tips.danger {
background: rgba(0, 0, 0, 0.6);
color: yellow;
}
</style>
<style>
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@ -0,0 +1,473 @@
<template>
<div class="drag-verify-container">
<div :style="dragVerifyImgStyle">
<img ref="checkImg" crossOrigin="anonymous" :src="imgsrc" @load="checkimgLoaded" style="width: 100%" alt="" />
<canvas ref="maincanvas" class="main-canvas"></canvas>
<canvas ref="movecanvas" :class="{ goFirst: isOk, goKeep: isKeep }" class="move-canvas"></canvas>
<div class="refresh" v-if="showRefresh && !isPassing">
<i :class="refreshIcon" @click="refreshimg"></i>
</div>
<div class="tips success" v-if="showTips && isPassing">{{ successTip }}</div>
<div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{ failTip }}</div>
</div>
<div
ref="dragVerify"
class="drag_verify"
:style="dragVerifyStyle"
@mousemove="dragMoving"
@mouseup="dragFinish"
@mouseleave="dragFinish"
@touchmove="dragMoving"
@touchend="dragFinish"
>
<div class="dv_progress_bar" :class="{ goFirst2: isOk }" ref="progressBar" :style="progressBarStyle">
{{ successMessage }}
</div>
<div class="dv_text" :style="textStyle" ref="message">
{{ message }}
</div>
<div
class="dv_handler dv_handler_bg"
:class="{ goFirst: isOk }"
@mousedown="dragStart"
@touchstart="dragStart"
ref="handler"
:style="handlerStyle"
>
<i :class="handlerIcon"></i>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dragVerifyImgChip',
props: {
isPassing: {
type: Boolean,
default: false,
},
width: {
type: Number,
default: 250,
},
height: {
type: Number,
default: 40,
},
text: {
type: String,
default: 'swiping to the right side',
},
successText: {
type: String,
default: 'success',
},
background: {
type: String,
default: '#eee',
},
progressBarBg: {
type: String,
default: '#76c61d',
},
completedBg: {
type: String,
default: '#76c61d',
},
circle: {
type: Boolean,
default: false,
},
radius: {
type: String,
default: '4px',
},
handlerIcon: {
type: String,
},
successIcon: {
type: String,
},
handlerBg: {
type: String,
default: '#fff',
},
textSize: {
type: String,
default: '14px',
},
textColor: {
type: String,
default: '#333',
},
imgsrc: {
type: String,
},
barWidth: {
type: Number,
default: 40,
},
barRadius: {
type: Number,
default: 8,
},
showRefresh: {
type: Boolean,
default: false,
},
refreshIcon: {
type: String,
},
showTips: {
type: Boolean,
default: true,
},
successTip: {
type: String,
default: '验证通过超过80%用户',
},
failTip: {
type: String,
default: '验证未通过,拖动滑块将悬浮图像正确合并',
},
diffWidth: {
type: Number,
default: 20,
},
},
mounted: function () {
const dragEl = this.$refs.dragVerify;
dragEl.style.setProperty('--textColor', this.textColor);
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px');
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px');
},
computed: {
handlerStyle: function () {
return {
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg,
};
},
message: function () {
return this.isPassing ? '' : this.text;
},
successMessage: function () {
return this.isPassing ? this.successText : '';
},
dragVerifyStyle: function () {
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius,
};
},
dragVerifyImgStyle: function () {
return {
width: this.width + 'px',
position: 'relative',
overflow: 'hidden',
};
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px' : this.radius,
};
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize,
};
},
},
data() {
return {
isMoving: false,
x: 0,
isOk: false,
isKeep: false,
clipBarx: 0,
showErrorTip: false,
};
},
methods: {
draw: function (ctx, x, y, operation) {
var l = this.barWidth;
var r = this.barRadius;
const PI = Math.PI;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
ctx.lineTo(x + l, y);
ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
ctx.lineTo(x + l, y + l);
ctx.lineTo(x, y + l);
ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
ctx.lineTo(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.stroke();
ctx[operation]();
ctx.globalCompositeOperation = 'destination-over';
},
checkimgLoaded: function () {
// 生成图片缺失位置
var barWidth = this.barWidth;
var imgHeight = this.$refs.checkImg.height;
var imgWidth = this.$refs.checkImg.width;
var halfWidth = Math.floor(this.width / 2);
var refreshHeigth = 25;
var tipHeight = 20;
var x = halfWidth + Math.ceil(Math.random() * (halfWidth - barWidth - this.barRadius - 5));
var y = refreshHeigth + Math.floor(Math.random() * (imgHeight - barWidth - refreshHeigth - tipHeight));
this.$refs.maincanvas.setAttribute('width', imgWidth);
this.$refs.maincanvas.setAttribute('height', imgHeight);
this.$refs.maincanvas.style.display = 'block';
var canvasCtx = this.$refs.maincanvas.getContext('2d');
this.draw(canvasCtx, x, y, 'fill');
this.clipBarx = x;
var moveCanvas = this.$refs.movecanvas;
moveCanvas.setAttribute('width', imgWidth);
moveCanvas.setAttribute('height', imgHeight);
this.$refs.movecanvas.style.display = 'block';
const L = barWidth + this.barRadius * 2 + 3; //实际宽度
var moveCtx = this.$refs.movecanvas.getContext('2d');
moveCtx.clearRect(0, 0, imgWidth, imgHeight);
this.draw(moveCtx, x, y, 'clip');
moveCtx.drawImage(this.$refs.checkImg, 0, 0, imgWidth, imgHeight);
var y = y - this.barRadius * 2 - 1;
const ImageData = moveCtx.getImageData(x, y, L, L);
moveCanvas.setAttribute('width', L);
moveCanvas.setAttribute('height', imgHeight);
moveCtx.putImageData(ImageData, 0, y);
},
dragStart: function (e) {
if (!this.isPassing) {
this.isMoving = true;
this.x = e.pageX || e.touches[0].pageX;
}
this.showBar = true;
this.showErrorTip = false;
this.$emit('handlerMove');
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x;
var handler = this.$refs.handler;
handler.style.left = _x + 'px';
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px';
this.$refs.movecanvas.style.left = _x + 'px';
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.changedTouches[0].pageX) - this.x;
if (Math.abs(_x - this.clipBarx) > this.diffWidth) {
this.isOk = true;
var that = this;
setTimeout(function () {
that.$refs.handler.style.left = '0';
that.$refs.progressBar.style.width = '0';
that.$refs.movecanvas.style.left = '0';
that.isOk = false;
}, 500);
this.$emit('passfail');
this.showErrorTip = true;
} else {
this.passVerify();
}
this.isMoving = false;
}
},
passVerify: function () {
this.$emit('update:isPassing', true);
this.isMoving = false;
var handler = this.$refs.handler;
handler.children[0].className = this.successIcon;
this.$refs.progressBar.style.background = this.completedBg;
this.$refs.message.style['-webkit-text-fill-color'] = 'unset';
this.$refs.message.style.animation = 'slidetounlock2 3s infinite';
this.$refs.progressBar.style.color = '#fff';
this.$refs.progressBar.style.fontSize = this.textSize;
this.isKeep = true;
setTimeout(() => {
this.$refs.movecanvas.style.left = this.clipBarx + 'px';
setTimeout(() => {
this.isKeep = false;
this.$refs.maincanvas.style.display = 'none';
this.$refs.movecanvas.style.display = 'none';
}, 200);
}, 100);
this.$emit('passcallback');
},
reset: function () {
this.reImg();
this.checkimgLoaded();
},
reImg: function () {
this.$emit('update:isPassing', false);
const oriData = this.$options.data();
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this[key] = oriData[key];
}
}
var handler = this.$refs.handler;
var message = this.$refs.message;
handler.style.left = '0';
this.$refs.progressBar.style.width = '0';
handler.children[0].className = this.handlerIcon;
message.style['-webkit-text-fill-color'] = 'transparent';
message.style.animation = 'slidetounlock 3s infinite';
message.style.color = this.background;
this.$refs.movecanvas.style.left = '0px';
},
refreshimg: function () {
this.$emit('refresh');
},
},
watch: {
imgsrc: {
immediate: false,
handler: function () {
this.reImg();
},
},
},
};
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goKeep {
transition: left 0.2s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
.drag-verify-container {
position: relative;
line-height: 0;
}
.refresh {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
font-size: 20px;
z-index: 200;
}
.tips {
position: absolute;
bottom: 0;
height: 20px;
line-height: 20px;
text-align: center;
width: 100%;
font-size: 12px;
z-index: 200;
}
.tips.success {
background: rgba(255, 255, 255, 0.6);
color: green;
}
.tips.danger {
background: rgba(0, 0, 0, 0.6);
color: yellow;
}
.main-canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.move-canvas {
position: absolute;
top: 0;
left: 0;
}
</style>
<style>
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@ -0,0 +1,434 @@
<template>
<div class="drag-verify-container">
<div :style="dragVerifyImgStyle">
<img ref="checkImg" :src="imgsrc" class="check-img" :class="{ goOrigin: isOk }" @load="checkimgLoaded" :style="imgStyle" alt="" />
<div class="tips success" v-if="showTips && isPassing">{{ successTip }}</div>
<div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{ failTip }}</div>
</div>
<div
ref="dragVerify"
class="drag_verify"
:style="dragVerifyStyle"
@mousemove="dragMoving"
@mouseup="dragFinish"
@mouseleave="dragFinish"
@touchmove="dragMoving"
@touchend="dragFinish"
>
<div class="dv_progress_bar" :class="{ goFirst2: isOk }" ref="progressBar" :style="progressBarStyle">
{{ successMessage }}
</div>
<div class="dv_text" :style="textStyle" ref="message">
{{ message }}
</div>
<div
class="dv_handler dv_handler_bg"
:class="{ goFirst: isOk }"
@mousedown="dragStart"
@touchstart="dragStart"
ref="handler"
:style="handlerStyle"
>
<i :class="handlerIcon"></i>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dragVerify',
props: {
isPassing: {
type: Boolean,
default: false,
},
width: {
type: Number,
default: 250,
},
height: {
type: Number,
default: 40,
},
text: {
type: String,
default: 'swiping to the right side',
},
successText: {
type: String,
default: 'success',
},
background: {
type: String,
default: '#eee',
},
progressBarBg: {
type: String,
default: '#76c61d',
},
completedBg: {
type: String,
default: '#76c61d',
},
circle: {
type: Boolean,
default: false,
},
radius: {
type: String,
default: '4px',
},
handlerIcon: {
type: String,
},
successIcon: {
type: String,
},
handlerBg: {
type: String,
default: '#fff',
},
textSize: {
type: String,
default: '14px',
},
textColor: {
type: String,
default: '#333',
},
imgsrc: {
type: String,
},
showTips: {
type: Boolean,
default: true,
},
successTip: {
type: String,
default: '验证通过',
},
failTip: {
type: String,
default: '验证失败',
},
diffDegree: {
type: Number,
default: 10,
},
minDegree: {
type: Number,
default: 90,
},
maxDegree: {
type: Number,
default: 270,
},
},
mounted: function () {
const dragEl = this.$refs.dragVerify;
dragEl.style.setProperty('--textColor', this.textColor);
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px');
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px');
},
computed: {
handlerStyle: function () {
return {
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg,
};
},
message: function () {
return this.isPassing ? '' : this.text;
},
successMessage: function () {
return this.isPassing ? this.successText : '';
},
dragVerifyStyle: function () {
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius,
};
},
dragVerifyImgStyle: function () {
return {
width: this.width + 'px',
height: this.width + 'px',
position: 'relative',
overflow: 'hidden',
'border-radius': '50%',
};
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px' : this.radius,
};
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize,
};
},
factor: function () {
//避免指定旋转角度时一直拖动到最右侧才验证通过
if (this.minDegree == this.maxDegree) {
return Math.floor(1 + Math.random() * 6) / 10 + 1;
}
return 1;
},
},
data() {
return {
isMoving: false,
x: 0,
isOk: false,
showBar: false,
showErrorTip: false,
ranRotate: 0,
cRotate: 0,
imgStyle: {},
};
},
methods: {
checkimgLoaded: function () {
//生成旋转角度
var minDegree = this.minDegree;
var maxDegree = this.maxDegree;
var ranRotate = Math.floor(minDegree + Math.random() * (maxDegree - minDegree)); //生成随机角度
this.ranRotate = ranRotate;
console.log('旋转' + ranRotate);
this.imgStyle = {
transform: `rotateZ(${ranRotate}deg)`,
};
},
dragStart: function (e) {
if (!this.isPassing) {
this.isMoving = true;
this.x = e.pageX || e.touches[0].pageX;
}
this.showBar = true;
this.showErrorTip = false;
this.$emit('handlerMove');
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x;
console.log(_x, '_x');
var handler = this.$refs.handler;
handler.style.left = _x + 'px';
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px';
var cRotate = Math.ceil((_x / (this.width - this.height)) * this.maxDegree * this.factor);
console.log(cRotate, 'cRotate');
this.cRotate = cRotate;
var rotate = this.ranRotate - cRotate;
this.imgStyle = {
transform: `rotateZ(${rotate}deg)`,
};
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
if (Math.abs(this.ranRotate - this.cRotate) > this.diffDegree) {
this.isOk = true;
this.imgStyle = {
transform: `rotateZ(${this.ranRotate}deg)`,
};
var that = this;
setTimeout(function () {
that.$refs.handler.style.left = '0';
that.$refs.progressBar.style.width = '0';
that.isOk = false;
}, 500);
this.showErrorTip = true;
this.$emit('passfail');
} else {
this.passVerify();
}
this.isMoving = false;
}
},
passVerify: function () {
this.$emit('update:isPassing', true);
this.isMoving = false;
var handler = this.$refs.handler;
handler.children[0].className = this.successIcon;
this.$refs.progressBar.style.background = this.completedBg;
this.$refs.message.style['-webkit-text-fill-color'] = 'unset';
this.$refs.message.style.animation = 'slidetounlock2 3s infinite';
this.$refs.progressBar.style.color = '#fff';
this.$refs.progressBar.style.fontSize = this.textSize;
this.$emit('passcallback');
},
reset: function () {
this.reImg();
this.checkimgLoaded();
},
reImg: function () {
this.$emit('update:isPassing', false);
const oriData = this.$options.data();
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this[key] = oriData[key];
}
}
var handler = this.$refs.handler;
var message = this.$refs.message;
handler.style.left = '0';
this.$refs.progressBar.style.width = '0';
handler.children[0].className = this.handlerIcon;
message.style['-webkit-text-fill-color'] = 'transparent';
message.style.animation = 'slidetounlock 3s infinite';
message.style.color = this.background;
},
refreshimg: function () {
this.$emit('refresh');
},
},
watch: {
imgsrc: {
immediate: false,
handler: function () {
this.reImg();
},
},
},
};
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goOrigin {
transition: transform 0.5s;
}
.goKeep {
transition: left 0.2s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
.drag-verify-container {
position: relative;
line-height: 0;
border-radius: 50%;
}
.move-bar {
position: absolute;
z-index: 100;
}
.clip-bar {
position: absolute;
background: rgba(255, 255, 255, 0.8);
}
.refresh {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
font-size: 20px;
z-index: 200;
}
.tips {
position: absolute;
bottom: 25px;
height: 20px;
line-height: 20px;
text-align: center;
width: 100%;
font-size: 12px;
z-index: 200;
}
.tips.success {
background: rgba(255, 255, 255, 0.6);
color: green;
}
.tips.danger {
background: rgba(0, 0, 0, 0.6);
color: yellow;
}
.check-img {
width: 100%;
border-radius: 50%;
}
</style>
<style>
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<div class="editor-container">
<div :id="id"></div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, watch } from 'vue';
import wangeditor from 'wangeditor';
export default {
name: 'wngEditor',
props: {
// 节点 id
id: {
type: String,
default: () => 'wangeditor',
},
// 是否禁用
isDisable: {
type: Boolean,
default: () => false,
},
// 内容框默认 placeholder
placeholder: {
type: String,
default: () => '请输入内容',
},
// 双向绑定
// 双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
},
setup(props, { emit }) {
const state = reactive({
editor: null,
});
// 初始化富文本
// https://doc.wangeditor.com/
const initWangeditor = () => {
state.editor = new wangeditor(`#${props.id}`);
state.editor.config.zIndex = 1;
state.editor.config.placeholder = props.placeholder;
state.editor.config.uploadImgShowBase64 = true;
state.editor.config.showLinkImg = false;
onWangeditorChange();
state.editor.create();
state.editor.txt.html(props.modelValue);
props.isDisable ? state.editor.disable() : state.editor.enable();
};
// 内容改变时
const onWangeditorChange = () => {
state.editor.config.onchange = (html: string) => {
emit('update:modelValue', html);
};
};
// 页面加载时
onMounted(() => {
initWangeditor();
});
// 监听双向绑定值的改变
// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
watch(
() => props.modelValue,
(value) => {
state.editor.txt.html(value);
}
);
return {
...toRefs(state),
};
},
};
</script>

View File

@ -0,0 +1,241 @@
<template>
<div class="icon-selector">
<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
<template #reference>
<el-input
v-model="fontIconSearch"
:placeholder="fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon
:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
class="font14"
v-if="fontIconPrefix === '' ? prepend?.indexOf('element') > -1 : fontIconPrefix?.indexOf('element') > -1"
/>
<i v-else :class="fontIconPrefix === '' ? prepend : fontIconPrefix" class="font14"></i>
</template>
</el-input>
</template>
<transition name="el-zoom-in-top">
<div class="icon-selector-warp" v-show="fontIconVisible">
<div class="icon-selector-warp-title flex">
<div class="flex-auto">{{ title }}</div>
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">ali</span>
<span :class="{ 'span-active': fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">ele</span>
<span :class="{ 'span-active': fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">awe</span>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
<div class="flex-margin">
<div class="icon-selector-warp-item-value">
<SvgIcon :name="v" />
</div>
</div>
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
</el-scrollbar>
</div>
</div>
</transition>
</el-popover>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch } from 'vue';
import initIconfont from '/@/utils/getStyleSheets';
export default {
name: 'iconSelector',
emits: ['update:modelValue', 'get', 'clear'],
props: {
// 输入框前置内容
prepend: {
type: String,
default: () => 'elementPointer',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'small',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
},
setup(props, { emit }) {
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state: any = reactive({
fontIconPrefix: '',
fontIconVisible: false,
fontIconWidth: 0,
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
});
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
setTimeout(() => {
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 处理 icon 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return false;
state.fontIconPlaceholder = props.modelValue;
state.fontIconPrefix = props.modelValue;
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
if (!state.fontIconSearch) return state.fontIconSheetsList;
let search = state.fontIconSearch.trim().toLowerCase();
return state.fontIconSheetsList.filter((item: any) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 初始化数据
const initFontIconData = async (type: string) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res: any) => {
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
});
} else if (type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconSheetsList = res;
});
} else if (type === 'awe') {
await initIconfont.awe().then((res: any) => {
// fontawesome字体图标使用 `fa xxx`
state.fontIconSheetsList = res.map((i) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
selectorScrollbarRef.value.wrap$.scrollTop = 0;
};
// 图标点击切换
const onIconChange = (type: string) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v: any) => {
state.fontIconPlaceholder = v;
state.fontIconVisible = false;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 页面加载时
onMounted(() => {
// 判断默认进来是什么类型图标,进行 tab 回显
if (props.type === 'all') {
if (props.modelValue?.indexOf('iconfont') > -1) onIconChange('ali');
else if (props.modelValue?.indexOf('element') > -1) onIconChange('ele');
else if (props.modelValue?.indexOf('fa') > -1) onIconChange('awe');
else onIconChange('ali');
} else {
onIconChange(props.type);
}
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,
...toRefs(state),
};
},
};
</script>

View File

@ -0,0 +1,194 @@
<template>
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
<div class="notice-bar-warp-slot" v-else><slot /></div>
</div>
<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
export default defineComponent({
name: 'noticeBar',
props: {
// 通知栏模式,可选值为 closeable link
mode: {
type: String,
default: () => '',
},
// 通知文本内容
text: {
type: String,
default: () => '',
},
// 通知文本颜色
color: {
type: String,
default: () => 'var(--color-warning)',
},
// 通知背景色
background: {
type: String,
default: () => 'var(--color-warning-light-9)',
},
// 字体大小单位px
size: {
type: [Number, String],
default: () => 14,
},
// 通知栏高度单位px
height: {
type: [Number, String],
default: () => 40,
},
// 动画延迟时间 (s)
delay: {
type: [Number, String],
default: () => 1,
},
// 滚动速率 (px/s)
speed: {
type: [Number, String],
default: () => 100,
},
// 是否开启垂直滚动
scrollable: {
type: Boolean,
default: () => false,
},
// 自定义左侧图标
leftIcon: {
type: String,
default: () => '',
},
// 自定义右侧图标
rightIcon: {
type: String,
default: () => '',
},
},
setup(props, { emit }) {
const noticeBarWarpRef = ref();
const noticeBarTextRef = ref();
const state = reactive({
order: 1,
oneTime: '',
twoTime: '',
warpOWidth: '',
textOWidth: '',
isMode: false,
});
// 初始化 animation 各项参数
const initAnimation = () => {
nextTick(() => {
state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
state.textOWidth = noticeBarTextRef.value.offsetWidth;
document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
computeAnimationTime();
setTimeout(() => {
changeAnimation();
}, props.delay * 1000);
});
};
// 计算 animation 滚动时长
const computeAnimationTime = () => {
state.oneTime = state.textOWidth / props.speed;
state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
};
// 改变 animation 动画调用
const changeAnimation = () => {
if (state.order === 1) {
noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
state.order = 2;
} else {
noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
}
};
// 监听 animation 动画的结束
const listenerAnimationend = () => {
noticeBarTextRef.value.addEventListener(
'animationend',
() => {
changeAnimation();
},
false
);
};
// 右侧 icon 图标点击
const onRightIconClick = () => {
if (!props.mode) return false;
if (props.mode === 'closeable') {
state.isMode = true;
emit('close');
} else if (props.mode === 'link') {
emit('link');
}
};
// 页面加载时
onMounted(() => {
if (props.scrollable) return false;
initAnimation();
listenerAnimationend();
});
return {
noticeBarWarpRef,
noticeBarTextRef,
onRightIconClick,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.notice-bar {
padding: 0 15px;
width: 100%;
border-radius: 4px;
.notice-bar-warp {
display: flex;
align-items: center;
width: 100%;
height: inherit;
.notice-bar-warp-text-box {
flex: 1;
height: inherit;
display: flex;
align-items: center;
overflow: hidden;
position: relative;
.notice-bar-warp-text {
white-space: nowrap;
position: absolute;
left: 0;
}
.notice-bar-warp-slot {
width: 100%;
white-space: nowrap;
::v-deep(.el-carousel__item) {
display: flex;
align-items: center;
}
}
}
.notice-bar-warp-left-icon {
width: 24px;
font-size: inherit !important;
}
.notice-bar-warp-right-icon {
width: 24px;
text-align: right;
font-size: inherit !important;
&:hover {
cursor: pointer;
}
}
}
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div>
<screen-short v-if="screenshotStatus" @destroy-component="destroyComponent" @get-image-data="getImageData"></screen-short>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, onUnmounted } from 'vue';
export default defineComponent({
name: 'screenShortComponent',
setup(props, { emit }) {
const state = reactive({
screenshotStatus: false,
});
// 打开截屏
const openScreenshot = () => {
state.screenshotStatus = true;
onMonitorKeyup();
};
// 销毁组件函数
const destroyComponent = (status: boolean) => {
state.screenshotStatus = status;
};
// 获取裁剪区域图片信息
const getImageData = (base64: string) => {
emit('getBase64', base64);
};
// 监听键盘 `esc` 按下
const onMonitorKeyup = () => {
if (!state.screenshotStatus) return false;
window.addEventListener('keydown', (e: any) => {
if (e.keyCode === 27) destroyComponent();
});
};
// 页面销毁时
onUnmounted(() => {
window.removeEventListener('keydown', () => {});
});
return {
openScreenshot,
destroyComponent,
getImageData,
onMonitorKeyup,
...toRefs(state),
};
},
});
</script>

View File

@ -0,0 +1,28 @@
<script lang="ts">
// 渲染函数https://v3.cn.vuejs.org/guide/render-function.html
import { h, resolveComponent, defineComponent } from 'vue';
export default defineComponent({
name: 'svgIcon',
props: {
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
},
// svg 颜色
color: {
type: String,
},
},
setup(props) {
if (props.name?.indexOf('element') > -1) {
return () => h('i', { class: 'el-icon', style: `--font-size: ${props.size};--color: ${props.color}` }, [h(resolveComponent(props.name))]);
} else {
return () => h('i', { class: props.name, style: `font-size: ${props.size};color: ${props.color}` });
}
},
});
</script>

View File

@ -1,60 +0,0 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import zhcnLocale from 'element-ui/lib/locale/lang/zh-CN';
import enLocale from 'element-ui/lib/locale/lang/en';
import zhtwLocale from 'element-ui/lib/locale/lang/zh-TW';
import store from '@/store/index.js';
import nextZhcn from '@/i18n/lang/zh-cn.js';
import nextEn from '@/i18n/lang/en.js';
import nextZhtw from '@/i18n/lang/zh-tw.js';
import pagesHomeZhcn from '@/i18n/pages/home/zh-cn.js';
import pagesHomeEn from '@/i18n/pages/home/en.js';
import pagesHomeZhtw from '@/i18n/pages/home/zh-tw.js';
import pagesLoginZhcn from '@/i18n/pages/login/zh-cn.js';
import pagesLoginEn from '@/i18n/pages/login/en.js';
import pagesLoginZhtw from '@/i18n/pages/login/zh-tw.js';
// 使用插件
Vue.use(VueI18n);
// 定义语言国际化内容
/**
* 说明:
* /src/i18n/lang 下的 js 为框架的国际化内容
* /src/i18n/pages 下的 js 为各界面的国际化内容
*/
const messages = {
'zh-cn': {
...zhcnLocale,
message: {
...nextZhcn,
...pagesHomeZhcn,
...pagesLoginZhcn,
},
},
en: {
...enLocale,
message: {
...nextEn,
...pagesHomeEn,
...pagesLoginEn,
},
},
'zh-tw': {
...zhtwLocale,
message: {
...nextZhtw,
...pagesHomeZhtw,
...pagesLoginZhtw,
},
},
};
// 导出语言国际化
export const i18n = new VueI18n({
locale: store.state.themeConfig.themeConfig.globalI18n,
fallbackLocale: 'zh-cn',
messages,
});

62
src/i18n/index.ts Normal file
View File

@ -0,0 +1,62 @@
import { createI18n } from 'vue-i18n';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import enLocale from 'element-plus/lib/locale/lang/en';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
import { store } from '/@/store/index';
import nextZhcn from '/@/i18n/lang/zh-cn';
import nextEn from '/@/i18n/lang/en';
import nextZhtw from '/@/i18n/lang/zh-tw';
import pagesHomeZhcn from '/@/i18n/pages/home/zh-cn';
import pagesHomeEn from '/@/i18n/pages/home/en';
import pagesHomeZhtw from '/@/i18n/pages/home/zh-tw';
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
import pagesLoginEn from '/@/i18n/pages/login/en';
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
// 定义语言国际化内容
/**
* 说明:
* /src/i18n/lang 下的 ts 为框架的国际化内容
* /src/i18n/pages 下的 ts 为各界面的国际化内容
*/
const messages = {
[zhcnLocale.name]: {
...zhcnLocale,
message: {
...nextZhcn,
...pagesHomeZhcn,
...pagesLoginZhcn,
...pagesFormI18nZhcn,
},
},
[enLocale.name]: {
...enLocale,
message: {
...nextEn,
...pagesHomeEn,
...pagesLoginEn,
...pagesFormI18nEn,
},
},
[zhtwLocale.name]: {
...zhtwLocale,
message: {
...nextZhtw,
...pagesHomeZhtw,
...pagesLoginZhtw,
...pagesFormI18nZhtw,
},
},
};
// 导出语言国际化
export const i18n = createI18n({
locale: store.state.themeConfig.themeConfig.globalI18n,
fallbackLocale: zhcnLocale.name,
messages,
});

View File

@ -4,7 +4,10 @@ export default {
home: 'home',
system: 'system',
systemMenu: 'systemMenu',
systemRole: 'systemRole',
systemUser: 'systemUser',
systemDept: 'systemDept',
systemDic: 'systemDic',
limits: 'limits',
limitsFrontEnd: 'FrontEnd',
limitsFrontEndPage: 'FrontEndPage',
@ -21,18 +24,20 @@ export default {
menu2: 'menu2',
funIndex: 'function',
funTagsView: 'funTagsView',
funSignCanvas: 'Online signature',
funCountup: 'countup',
funEchartsTree: 'echartsTree',
funSelector: 'funSelector',
funNoticeBar: 'ScrollingNoticeBar',
funWangEditor: 'wangEditor',
funCropper: 'cropper',
funMindMap: 'G6 MindMap',
funQrcode: 'qrcode',
funEchartsMap: 'EchartsMap',
funPrintJs: 'PrintJs',
funClipboard: 'Copy cut',
funScreenShort: 'screenCapture',
funGridLayout: 'Drag layout',
funSplitpanes: 'Pane splitter',
funDragVerify: 'Validator',
pagesIndex: 'pages',
pagesFiltering: 'Filtering',
pagesFilteringDetails: 'FilteringDetails',
@ -42,10 +47,27 @@ export default {
pagesAwesome: 'awesome icon',
pagesCityLinkage: 'CityLinkage',
pagesFormAdapt: 'FormAdapt',
pagesFormI18n: 'FormI18n',
pagesFormRules: 'Multi form validation',
pagesDynamicForm: 'Dynamic complex form',
pagesWorkflow: 'Workflow',
pagesListAdapt: 'ListAdapt',
pagesWaterfall: 'Waterfall',
pagesSteps: 'Steps',
pagesPreview: 'Large preview',
pagesWaves: 'Wave effect',
pagesTree: 'tree alter table',
pagesDrag: 'Drag command',
pagesLazyImg: 'Image lazy loading',
paramsIndex: 'Routing parameters',
paramsCommon: 'General routing',
paramsDynamic: 'Dynamic routing',
paramsCommonDetails: 'General routing details',
paramsDynamicDetails: 'Dynamic routing details',
chartIndex: 'chartIndex',
visualizingIndex: 'visualizingIndex',
visualizingLinkDemo1: 'visualizingLinkDemo1',
visualizingLinkDemo2: 'visualizingLinkDemo2',
personal: 'personal',
tools: 'tools',
layoutLinkView: 'LinkView',
@ -92,6 +114,7 @@ export default {
closeOther: 'closeOther',
closeAll: 'closeAll',
fullscreen: 'fullscreen',
closeFullscreen: 'closeFullscreen',
},
notFound: {
foundTitle: 'Wrong address input, please re-enter the address~',
@ -115,6 +138,7 @@ export default {
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsTopBarColorGradual: 'Top bar gradient',
twoIsMenuBarColorGradual: 'Menu gradient',
twoIsColumnsMenuBarColorGradual: 'Column gradient',
twoIsMenuBarColorHighlight: 'Menu font highlight',
threeTitle: 'Interface settings',
threeIsCollapse: 'Menu horizontal collapse',
@ -131,6 +155,7 @@ export default {
fourIsTagsviewIcon: 'Open tagsview Icon',
fourIsCacheTagsView: 'Enable tagsview cache',
fourIsSortableTagsView: 'Enable tagsview drag',
fourIsShareTagsView: 'Enable tagsview sharing',
fourIsFooter: 'Open footer',
fourIsGrayscale: 'Grey model',
fourIsInvert: 'Color weak mode',
@ -147,18 +172,10 @@ export default {
sixClassic: 'Two',
sixTransverse: 'Three',
sixColumns: 'Four',
tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.js` It has been modified in.',
tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.ts` It has been modified in.',
copyText: 'replication configuration',
resetText: 'restore default',
copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!',
},
upgrade: {
title: 'New version',
msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
desc: 'Prompt: Update will restore the default configuration',
btnOne: 'Cruel refusal',
btnTwo: 'Update now',
btnTwoLoading: 'Updating',
},
};

View File

@ -4,7 +4,10 @@ export default {
home: '首页',
system: '系统设置',
systemMenu: '菜单管理',
systemRole: '角色管理',
systemUser: '用户管理',
systemDept: '部门管理',
systemDic: '字典管理',
limits: '权限管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '页面权限',
@ -21,18 +24,20 @@ export default {
menu2: '菜单2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funSignCanvas: '在线签名',
funCountup: 'countup 数字滚动',
funEchartsTree: 'echartsTree 树图',
funSelector: '图标选择器',
funNoticeBar: '滚动通知栏',
funWangEditor: 'wangEditor 编辑器',
funCropper: 'cropper 图片裁剪',
funMindMap: 'G6 思维导图',
funQrcode: 'qrcode 二维码生成',
funEchartsMap: '地理坐标/地图',
funPrintJs: '页面打印',
funClipboard: '复制剪切',
funScreenShort: 'web端自定义截屏',
funGridLayout: '拖拽布局',
funSplitpanes: '窗格拆分器',
funDragVerify: '验证器',
pagesIndex: '页面',
pagesFiltering: '过滤筛选组件',
pagesFilteringDetails: '过滤筛选组件详情',
@ -42,10 +47,27 @@ export default {
pagesAwesome: 'awesome 字体图标',
pagesCityLinkage: '城市多级联动',
pagesFormAdapt: '表单自适应',
pagesFormI18n: '表单国际化',
pagesFormRules: '多表单验证',
pagesDynamicForm: '动态复杂表单',
pagesWorkflow: '工作流',
pagesListAdapt: '列表自适应',
pagesWaterfall: '瀑布屏',
pagesSteps: '步骤条',
pagesPreview: '大图预览',
pagesWaves: '波浪效果',
pagesTree: '树形改表格',
pagesDrag: '拖动指令',
pagesLazyImg: '图片懒加载',
paramsIndex: '路由参数',
paramsCommon: '普通路由',
paramsDynamic: '动态路由',
paramsCommonDetails: '普通路由详情',
paramsDynamicDetails: '动态路由详情',
chartIndex: '大数据图表',
visualizingIndex: '数据可视化',
visualizingLinkDemo1: '数据可视化演示1',
visualizingLinkDemo2: '数据可视化演示2',
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
@ -92,6 +114,7 @@ export default {
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
},
notFound: {
foundTitle: '地址输入错误,请重新输入地址~',
@ -115,6 +138,7 @@ export default {
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsMenuBarColorHighlight: '菜单字体背景高亮',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
@ -131,6 +155,7 @@ export default {
fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsShareTagsView: '开启 TagsView 共用',
fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
@ -147,18 +172,10 @@ export default {
sixClassic: '经典',
sixTransverse: '横向',
sixColumns: '分栏',
tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.js` 中修改。',
tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.ts` 中修改。',
copyText: '一键复制配置',
resetText: '一键恢复默认',
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配置',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
};

View File

@ -4,7 +4,10 @@ export default {
home: '首頁',
system: '系統設置',
systemMenu: '選單管理',
systemRole: '角色管理',
systemUser: '用戶管理',
systemDept: '部門管理',
systemDic: '字典管理',
limits: '許可權管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '頁面許可權',
@ -21,18 +24,20 @@ export default {
menu2: '選單2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funSignCanvas: '線上簽名',
funCountup: 'countup 數位滾動',
funEchartsTree: 'echartsTree 樹圖',
funSelector: '圖標選擇器',
funNoticeBar: '滾動通知欄',
funWangEditor: 'wangEditor 編輯器',
funCropper: 'cropper 圖片裁剪',
funMindMap: 'G6 心智圖',
funQrcode: 'qrcode 二維碼生成',
funEchartsMap: '地理座標/地圖',
funPrintJs: '頁面列印',
funClipboard: '複製剪切',
funScreenShort: '自定義截圖',
funGridLayout: '拖拽佈局',
funSplitpanes: '窗格折開器',
funDragVerify: '驗證器',
pagesIndex: '頁面',
pagesFiltering: '過濾篩選組件',
pagesFilteringDetails: '過濾篩選組件詳情',
@ -42,10 +47,27 @@ export default {
pagesAwesome: 'awesome 字體圖標',
pagesCityLinkage: '都市多級聯動',
pagesFormAdapt: '表單自我調整',
pagesFormI18n: '表單國際化',
pagesFormRules: '多表單驗證',
pagesDynamicForm: '動態複雜表單',
pagesWorkflow: '工作流',
pagesListAdapt: '清單自我調整',
pagesWaterfall: '瀑布屏',
pagesSteps: '步驟條',
pagesPreview: '大圖預覽',
pagesWaves: '波浪效果',
pagesTree: '樹形改表格',
pagesDrag: '拖動指令',
pagesLazyImg: '圖片懶加載',
paramsIndex: '路由參數',
paramsCommon: '普通路由',
paramsDynamic: '動態路由',
paramsCommonDetails: '普通路由詳情',
paramsDynamicDetails: '動態路由詳情',
chartIndex: '大資料圖表',
visualizingIndex: '數據視覺化',
visualizingLinkDemo1: '數據視覺化演示1',
visualizingLinkDemo2: '數據視覺化演示2',
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
@ -92,6 +114,7 @@ export default {
closeOther: '關閉其它',
closeAll: '全部關閉',
fullscreen: '當前頁全屏',
closeFullscreen: '關閉全屏',
},
notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~',
@ -115,6 +138,7 @@ export default {
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsTopBarColorGradual: '頂欄背景漸變',
twoIsMenuBarColorGradual: '選單背景漸變',
twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
twoIsMenuBarColorHighlight: '選單字體背景高亮',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
@ -131,6 +155,7 @@ export default {
fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsShareTagsView: '開啟 TagsView 共用',
fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
@ -147,18 +172,10 @@ export default {
sixClassic: '經典',
sixTransverse: '橫向',
sixColumns: '分欄',
tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.js`中修改。',
tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.ts`中修改。',
copyText: '一鍵複製配寘',
resetText: '一鍵恢復默認',
copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!',
},
upgrade: {
title: '新版本陞級',
msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
desc: '提示:更新會還原默認配寘',
btnOne: '殘忍拒絕',
btnTwo: '馬上更新',
btnTwoLoading: '更新中',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: 'name',
email: 'email',
autograph: 'autograph',
},
formI18nPlaceholder: {
name: 'Please enter your name',
email: 'Please enter the users Department',
autograph: 'Please enter the login account name',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用户归属部门',
autograph: '登陆账户名',
},
formI18nPlaceholder: {
name: '请输入姓名',
email: '请输入用户归属部门',
autograph: '请输入登陆账户名',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用戶歸屬部門',
autograph: '登入帳戶名',
},
formI18nPlaceholder: {
name: '請輸入姓名',
email: '請輸入用戶歸屬部門',
autograph: '請輸入登入帳戶名',
},
};

View File

@ -1,14 +0,0 @@
// 定义内容
export default {
card: {
title1: 'My desk',
title2: 'Message notification',
title3: 'more',
title4: 'Marketing recommendation',
title5: 'more',
title6: 'Inventory operations',
title7: 'Performance',
title8: 'Out of stock monitoring',
title9: 'Performance overtime warning',
},
};

15
src/i18n/pages/home/en.ts Normal file
View File

@ -0,0 +1,15 @@
// 定义内容
export default {
card: {
title1: 'Commodity sales',
title2: 'environmental monitoring',
title3: 'Early warning information',
title4: 'dynamic information',
title5: 'Performance overtime warning',
},
table: {
th1: 'time',
th2: 'Laboratory name',
th3: 'Alarm content',
},
};

View File

@ -1,14 +0,0 @@
// 定义内容
export default {
card: {
title1: '我的工作台',
title2: '消息通知',
title3: '更多',
title4: '营销推荐',
title5: '更多',
title6: '库存作业',
title7: '履约情况',
title8: '缺货监控',
title9: '履约超时预警',
},
};

View File

@ -0,0 +1,15 @@
// 定义内容
export default {
card: {
title1: '商品销售情况',
title2: '环境监测',
title3: '预警信息',
title4: '动态信息',
title5: '履约超时预警',
},
table: {
th1: '时间',
th2: '实验室名称',
th3: '报警内容',
},
};

View File

@ -1,14 +0,0 @@
// 定义内容
export default {
card: {
title1: '我的工作臺',
title2: '消息通知',
title3: '更多',
title4: '行銷推薦',
title5: '更多',
title6: '庫存工作',
title7: '履約情况',
title8: '缺貨監控',
title9: '履約超時預警',
},
};

View File

@ -0,0 +1,15 @@
// 定义内容
export default {
card: {
title1: '商品銷售情况',
title2: '環境監測',
title3: '預警資訊',
title4: '動態資訊',
title5: '履約超時預警',
},
table: {
th1: '時間',
th2: '實驗室名稱',
th3: '報警內容',
},
};

View File

@ -1,18 +0,0 @@
// 定义内容
export default {
login: {
placeholder1: 'The user name admin or not is test',
placeholder2: 'Password: 123456',
placeholder3: 'Please enter the verification code',
btnText: 'Sign in',
link: {
one1: 'Third party login',
one2: 'Links',
},
signInText: 'welcome back!',
copyright: {
one5: 'Copyright: Shenzhen XXX Software Technology Co., Ltd',
two6: 'Copyright: Shenzhen XXX software technology Guangdong ICP preparation no.05010000',
},
},
};

View File

@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: 'Account password',
two2: 'Mobile number',
},
link: {
one3: 'Third party login',
two4: 'Links',
},
copyright: {
one5: 'Copyright: Shenzhen XXX Software Technology Co., Ltd',
two6: 'Copyright: Shenzhen XXX software technology Guangdong ICP preparation no.05010000',
},
account: {
accountPlaceholder1: 'The user name admin or not is test',
accountPlaceholder2: 'Password: 123456',
accountPlaceholder3: 'Please enter the verification code',
accountBtnText: 'Sign in',
},
mobile: {
placeholder1: 'Please input mobile phone number',
placeholder2: 'Please enter the verification code',
codeText: 'Get code',
btnText: 'Sign in',
},
signInText: 'welcome back!',
};

View File

@ -1,18 +0,0 @@
// 定义内容
export default {
login: {
placeholder1: '用户名 admin 或不输均为 test',
placeholder2: '密码123456',
placeholder3: '请输入验证码',
btnText: '登 录',
link: {
one1: '第三方登录',
one2: '友情链接',
},
signInText: '欢迎回来!',
copyright: {
one5: '版权所有深圳市xxx软件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粤ICP备05010000号',
},
},
};

View File

@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '账号密码登录',
two2: '手机号登录',
},
link: {
one3: '第三方登录',
two4: '友情链接',
},
copyright: {
one5: '版权所有深圳市xxx软件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粤ICP备05010000号',
},
account: {
accountPlaceholder1: '用户名 admin 或不输均为 test',
accountPlaceholder2: '密码123456',
accountPlaceholder3: '请输入验证码',
accountBtnText: '登 录',
},
mobile: {
placeholder1: '请输入手机号',
placeholder2: '请输入验证码',
codeText: '获取验证码',
btnText: '登 录',
},
signInText: '欢迎回来!',
};

View File

@ -1,18 +0,0 @@
// 定义内容
export default {
login: {
placeholder1: '用戶名admin或不輸均為test',
placeholder2: '密碼123456',
placeholder3: '請輸入驗證碼',
btnText: '登 录',
link: {
one1: '協力廠商登入',
one2: '友情連結',
},
signInText: '歡迎回來!',
copyright: {
one5: '版權所有深圳市xxx軟件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粵ICP備05010000號',
},
},
};

View File

@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '帳號密碼登入',
two2: '手機號登入',
},
link: {
one3: '協力廠商登入',
two4: '友情連結',
},
copyright: {
one5: '版權所有深圳市xxx軟件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粵ICP備05010000號',
},
account: {
accountPlaceholder1: '用戶名admin或不輸均為test',
accountPlaceholder2: '密碼123456',
accountPlaceholder3: '請輸入驗證碼',
accountBtnText: '登入',
},
mobile: {
placeholder1: '請輸入手機號',
placeholder2: '請輸入驗證碼',
codeText: '獲取驗證碼',
btnText: '登入',
},
signInText: '歡迎回來!',
};

View File

@ -1,106 +1,159 @@
<template>
<el-aside class="layout-aside" :class="setCollapseWidth" v-if="clientWidth > 1000">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideRef">
<Vertical :menuList="menuList" :class="setCollapseWidth" />
</el-scrollbar>
</el-aside>
<el-drawer :visible.sync="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="220px" v-else>
<el-aside class="layout-aside w100 h100">
<div class="h100" v-show="!isTagsViewCurrenFull">
<el-aside class="layout-aside" :class="setCollapseStyle">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideRef">
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
<Vertical :menuList="menuList" />
</el-scrollbar>
</el-aside>
</el-drawer>
</div>
</template>
<script>
import Vertical from '@/layout/navMenu/vertical.vue';
import Logo from '@/layout/logo/index.vue';
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount } from 'vue';
import { useStore } from '/@/store/index';
import Logo from '/@/layout/logo/index.vue';
import Vertical from '/@/layout/navMenu/vertical.vue';
export default {
name: 'layoutAside',
components: { Vertical, Logo },
data() {
return {
components: { Logo, Vertical },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const state: any = reactive({
menuList: [],
clientWidth: '',
};
},
computed: {
// 设置左侧菜单的具体宽度
setCollapseWidth() {
let { layout, isCollapse } = this.$store.state.themeConfig.themeConfig;
let asideBrColor = '';
layout === 'classic' || layout === 'columns' ? (asideBrColor = 'layout-el-aside-br-color') : '';
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 设置菜单展开/收起时的宽度
const setCollapseStyle = computed(() => {
const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
const asideBrColor =
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
// 判断是否是手机端
if (state.clientWidth <= 1000) {
if (isCollapse) {
return ['layout-aside-width1', asideBrColor];
document.body.setAttribute('class', 'el-popup-parent--hidden');
const asideEle = document.querySelector('.layout-container') as HTMLElement;
const modeDivs = document.createElement('div');
modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
asideEle.appendChild(modeDivs);
modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
} else {
return ['layout-aside-width-default', asideBrColor];
// 关闭弹窗
closeLayoutAsideMobileMode();
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
}
} else {
// 其它布局给 64px
if (isCollapse) {
return ['layout-aside-width64', asideBrColor];
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-1'];
} else {
return [asideBrColor, 'layout-aside-pc-220'];
}
} else {
return ['layout-aside-width-default', asideBrColor];
// 其它布局给 64px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-64'];
} else {
return [asideBrColor, 'layout-aside-pc-220'];
}
}
}
},
// 设置 logo 是否显示
setShowLogo() {
let { layout, isShowLogo } = this.$store.state.themeConfig.themeConfig;
});
// 关闭移动端蒙版
const closeLayoutAsideMobileMode = () => {
const el = document.querySelector('.layout-aside-mobile-mode');
el && el.parentNode?.removeChild(el);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) store.state.themeConfig.themeConfig.isCollapse = false;
document.body.setAttribute('class', '');
};
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
},
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
this.initMenuFixed(document.body.clientWidth);
this.setFilterRoutes();
this.bus.$on('setSendColumnsChildren', (res) => {
this.menuList = res.children;
});
this.bus.$on('layoutMobileResize', (res) => {
this.initMenuFixed(res.clientWidth);
});
// 菜单滚动条监听
this.bus.$on('updateElScrollBar', () => {
setTimeout(() => {
this.$refs.layoutAsideRef.update();
}, 300);
});
},
methods: {
// 设置/过滤路由(非静态路由/是否显示在菜单中)
setFilterRoutes() {
if (this.$store.state.themeConfig.themeConfig.layout === 'columns') return false;
this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
},
// 设置/过滤路由 递归函数
filterRoutesFun(arr) {
const setFilterRoutes = () => {
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
state.menuList = filterRoutesFun(store.state.routesList.routesList);
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
},
};
// 设置菜单导航是否固定(移动端)
initMenuFixed(clientWidth) {
this.clientWidth = clientWidth;
},
},
// 页面销毁时
destroyed() {
// 取消菜单滚动条监听
this.bus.$off('updateElScrollBar', () => {});
const initMenuFixed = (clientWidth: number) => {
state.clientWidth = clientWidth;
};
// 鼠标移入、移出
const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = store.state.themeConfig.themeConfig;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
store.dispatch('routesList/setColumnsMenuHover', bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
}
});
// 监听vuex值的变化动态赋值给菜单中
watch(store.state, (val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
});
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
setCollapseStyle,
setShowLogo,
getThemeConfig,
isTagsViewCurrenFull,
onAsideEnterLeave,
...toRefs(state),
};
},
};
</script>

View File

@ -1,18 +1,23 @@
<template>
<div class="layout-columns-aside">
<el-scrollbar>
<ul>
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
<li
v-for="(v, k) in columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v)"
ref="columnsAsideOffsetTopRefs"
:class="{ 'layout-columns-active': liIndex === k }"
@click="onColumnsAsideMenuClick(v, k)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
:ref="
(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
:title="$t(v.meta.title)"
>
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<i :class="v.meta.icon"></i>
<div class="font12">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
@ -22,8 +27,8 @@
</div>
<div :class="setColumnsAsidelayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<i :class="v.meta.icon"></i>
<div class="font12">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
@ -39,77 +44,89 @@
</div>
</template>
<script>
<script lang="ts">
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
export default {
name: 'layoutColumnsAside',
data() {
return {
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
columnsAsideList: [],
liIndex: 0,
liOldIndex: null,
liHoverIndex: null,
liOldPath: null,
difference: 0,
routeSplit: [],
};
},
computed: {
isNavHover: false,
});
// 设置分栏高亮风格
setColumnsAsideStyle() {
return this.$store.state.themeConfig.themeConfig.columnsAsideStyle;
},
const setColumnsAsideStyle = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideStyle;
});
// 设置分栏布局风格
setColumnsAsidelayout() {
return this.$store.state.themeConfig.themeConfig.columnsAsideLayout;
},
},
mounted() {
this.setFilterRoutes();
},
methods: {
const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout;
});
// 设置菜单高亮位置移动
setColumnsAsideMove(k) {
if (k === undefined) return false;
const els = this.$refs.columnsAsideOffsetTopRefs;
this.liIndex = k;
this.$refs.columnsAsideActiveRef.style.top = `${els[k].offsetTop + this.difference}px`;
},
const setColumnsAsideMove = (k: number) => {
state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
};
// 菜单高亮点击事件
onColumnsAsideMenuClick(v) {
let { path, redirect } = v;
if (redirect) this.$router.push(redirect);
else this.$router.push(path);
// 一个路由设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
if (!v.children || v.children.length <= 1) this.$store.state.themeConfig.themeConfig.isCollapse = true;
else if (v.children.length > 1) this.$store.state.themeConfig.themeConfig.isCollapse = false;
},
const onColumnsAsideMenuClick = (v: Object, k: number) => {
setColumnsAsideMove(k);
let { path, redirect } = v as any;
if (redirect) router.push(redirect);
else router.push(path);
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: Object, k: number) => {
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
store.dispatch('routesList/setColumnsMenuHover', false);
store.dispatch('routesList/setColumnsNavHover', true);
state.isNavHover = true;
};
// 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => {
await store.dispatch('routesList/setColumnsNavHover', false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
}, 100);
// state.isNavHover = false;
};
// 设置高亮动态位置
onColumnsAsideDown(k) {
this.$nextTick(() => {
this.setColumnsAsideMove(k);
const onColumnsAsideDown = (k: number) => {
nextTick(() => {
setColumnsAsideMove(k);
});
},
// 设置只有一个路由时设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6UW2I
setMenuAutoCollaps(path) {
const resData = this.setSendChildren(path);
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
resData.children.length <= 1
? (this.$store.state.themeConfig.themeConfig.isCollapse = true)
: (this.$store.state.themeConfig.themeConfig.isCollapse = false);
return resData;
},
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
setFilterRoutes() {
if (this.$store.state.routesList.routesList.length <= 0) return false;
this.columnsAsideList = this.filterRoutesFun(this.$store.state.routesList.routesList);
const resData = this.setMenuAutoCollaps(this.$route.path);
this.onColumnsAsideDown(resData.item[0].k);
this.bus.$emit('setSendColumnsChildren', resData);
},
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
setSendChildren(path) {
const setSendChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData = {};
this.columnsAsideList.map((v, k) => {
let currentData: any = {};
state.columnsAsideList.map((v: any, k: number) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
@ -118,49 +135,70 @@ export default {
}
});
return currentData;
},
};
// 路由过滤递归函数
filterRoutesFun(arr) {
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
},
};
// tagsView 点击时,根据路由查找下标 columnsAsideList实现左侧菜单高亮
setColumnsMenuHighlight(path) {
this.routeSplit = path.split('/');
this.routeSplit.shift();
const routeFirst = `/${this.routeSplit[0]}`;
const currentSplitRoute = this.columnsAsideList.find((v) => v.path === routeFirst);
const setColumnsMenuHighlight = (path: string) => {
state.routeSplit = path.split('/');
state.routeSplit.shift();
const routeFirst = `/${state.routeSplit[0]}`;
const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
this.onColumnsAsideDown(currentSplitRoute.k);
onColumnsAsideDown(currentSplitRoute.k);
}, 0);
},
},
watch: {
// 监听 vuex 数据变化
'$store.state': {
handler(val) {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (this.difference = 3) : (this.difference = 0);
if (val.routesList.routesList.length === this.columnsAsideList.length) return false;
this.setFilterRoutes();
},
deep: true,
},
// 监听路由的变化
$route: {
handler(to) {
const resData = this.setMenuAutoCollaps(to.path);
this.setColumnsMenuHighlight(to.path);
this.bus.$emit('setSendColumnsChildren', resData);
},
deep: true,
},
};
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch(store.state, (val) => {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
state.liHoverIndex = null;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
});
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
columnsAsideOffsetTopRefs,
columnsAsideActiveRef,
onColumnsAsideDown,
setColumnsAsideStyle,
setColumnsAsidelayout,
onColumnsAsideMenuClick,
onColumnsAsideMenuMouseenter,
onColumnsAsideMenuMouseleave,
...toRefs(state),
};
},
};
</script>
@ -169,11 +207,11 @@ export default {
.layout-columns-aside {
width: 70px;
height: 100%;
background: var(--prev-bg-columnsMenuBar);
background: var(--bg-columnsMenuBar);
ul {
position: relative;
li {
color: var(--prev-bg-columnsMenuBarColor);
color: var(--bg-columnsMenuBarColor);
width: 100%;
height: 50px;
text-align: center;
@ -205,21 +243,26 @@ export default {
}
a {
text-decoration: none;
color: var(--prev-bg-columnsMenuBarColor);
color: var(--bg-columnsMenuBarColor);
}
}
.layout-columns-active,
.layout-columns-active a {
color: var(--prev-color-text-white);
.layout-columns-active {
color: var(--color-whites) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--color-primary);
a {
color: var(--color-primary);
}
}
.columns-round {
background: var(--prev-color-primary);
color: var(--prev-color-text-white);
background: var(--color-primary);
color: var(--color-whites);
position: absolute;
left: 50%;
top: 2px;
height: 50px;
height: 44px;
width: 65px;
transform: translateX(-50%);
z-index: 0;

View File

@ -1,24 +1,32 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight">
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script>
import NavBarsIndex from '@/layout/navBars/index.vue';
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default {
name: 'layoutHeader',
components: { NavBarsIndex },
data() {
return {};
},
computed: {
// 设置顶部 header 的具体高度
setHeaderHeight() {
let { isTagsview, layout } = this.$store.state.themeConfig.themeConfig;
setup() {
const store = useStore();
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
},
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},
};
</script>

View File

@ -3,91 +3,73 @@
<el-scrollbar
class="layout-scrollbar"
ref="layoutScrollbarRef"
v-show="!currentRouteMeta.isLink && !currentRouteMeta.isIframe"
:style="{ minHeight: `calc(100vh - ${headerHeight}` }"
:style="{
minHeight: `calc(100vh - ${headerHeight})`,
padding: currentRouteMeta.isLink && currentRouteMeta.isIframe ? 0 : '',
transition: 'padding 0.3s ease-in-out',
}"
>
<LayoutParentView />
<Footers v-if="getThemeConfig.isFooter" />
<Footer v-if="getThemeConfig.isFooter" />
</el-scrollbar>
<Links
:style="{ height: `calc(100vh - ${headerHeight}` }"
:meta="currentRouteMeta"
v-if="currentRouteMeta.isLink && !currentRouteMeta.isIframe"
/>
<Iframes
:style="{ height: `calc(100vh - ${headerHeight}` }"
:meta="currentRouteMeta"
v-if="currentRouteMeta.isLink && currentRouteMeta.isIframe && isShowLink"
@getCurrentRouteMeta="onGetCurrentRouteMeta"
/>
</el-main>
</template>
<script>
import LayoutParentView from '@/layout/routerView/parent.vue';
import Footers from '@/layout/footer/index.vue';
import Links from '@/layout/routerView/link.vue';
import Iframes from '@/layout/routerView/iframes.vue';
export default {
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
import { useStore } from '/@/store/index';
import { useRoute } from 'vue-router';
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footer from '/@/layout/footer/index.vue';
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footers, Links, Iframes },
data() {
return {
components: { LayoutParentView, Footer },
setup() {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state = reactive({
headerHeight: '',
currentRouteMeta: {},
isShowLink: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 main 的高度
const initHeaderHeight = () => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return (state.headerHeight = `84px`);
else return (state.headerHeight = `50px`);
};
// 初始化获取当前路由 meta用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onBeforeMount(() => {
initHeaderHeight();
initGetMeta();
});
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
state.headerHeight = val.isTagsview ? '84px' : '50px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!proxy.$refs.layoutScrollbarRef) return false;
proxy.$refs.layoutScrollbarRef.update();
}
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
}
);
return {
getThemeConfig,
...toRefs(state),
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
mounted() {
this.initHeaderHeight();
this.initCurrentRouteMeta(this.$route.meta);
},
methods: {
// 初始化当前路由 meta 信息
initCurrentRouteMeta(meta) {
this.isShowLink = false;
this.currentRouteMeta = meta;
setTimeout(() => {
this.isShowLink = true;
}, 100);
},
// 设置 main 的高度
initHeaderHeight() {
let { isTagsview } = this.$store.state.themeConfig.themeConfig;
if (isTagsview) return (this.headerHeight = `84px`);
else return (this.headerHeight = `50px`);
},
// 子组件触发更新
onGetCurrentRouteMeta() {
this.initCurrentRouteMeta(this.$route.meta);
},
},
watch: {
// 监听 vuex 数据变化
'$store.state.themeConfig.themeConfig': {
handler(val) {
this.headerHeight = val.isTagsview ? '84px' : '50px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!this.$refs.layoutScrollbarRef) return false;
this.$refs.layoutScrollbarRef.update();
}
},
deep: true,
},
// 监听路由的变化
$route: {
handler(to) {
this.initCurrentRouteMeta(to.meta);
this.$refs.layoutScrollbarRef.wrap.scrollTop = 0;
},
deep: true,
},
},
};
});
</script>

View File

@ -1,17 +1,31 @@
<template>
<div class="layout-footer mt15">
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer-warp">
<div>vue-prev-adminMade by lyt with </div>
<div class="mt5">{{ $t('message.login.copyright.one5') }}</div>
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">{{ $t('message.copyright.one5') }}</div>
</div>
</div>
</template>
<script>
<script lang="ts">
import { toRefs, reactive } from 'vue';
import { onBeforeRouteUpdate } from 'vue-router';
export default {
name: 'layoutFooter',
data() {
return {};
setup() {
const state = reactive({
isDelayFooter: true,
});
// 路由改变时,等主界面动画加载完毕再显示 footer
onBeforeRouteUpdate(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
});
return {
...toRefs(state),
};
},
};
</script>
@ -22,8 +36,9 @@ export default {
display: flex;
&-warp {
margin: auto;
color: var(--prev-color-text-secondary);
color: var(--el-text-color-secondary);
text-align: center;
animation: logoAnimation 0.3s ease-in-out;
}
}
</style>

View File

@ -5,47 +5,53 @@
<Columns v-else-if="getThemeConfig.layout === 'columns'" />
</template>
<script>
import { Local } from '@/utils/storage.js';
<script lang="ts">
import { computed, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index';
import { Local } from '/@/utils/storage';
import Defaults from '/@/layout/main/defaults.vue';
import Classic from '/@/layout/main/classic.vue';
import Transverse from '/@/layout/main/transverse.vue';
import Columns from '/@/layout/main/columns.vue';
export default {
name: 'layout',
components: {
Defaults: () => import('@/layout/main/defaults.vue'),
Classic: () => import('@/layout/main/classic.vue'),
Transverse: () => import('@/layout/main/transverse.vue'),
Columns: () => import('@/layout/main/columns.vue'),
},
computed: {
components: { Defaults, Classic, Transverse, Columns },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
this.onLayoutResize();
window.addEventListener('resize', this.onLayoutResize);
},
methods: {
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 窗口大小改变时(适配移动端)
onLayoutResize() {
if (!Local.get('oldLayout')) Local.set('oldLayout', this.$store.state.themeConfig.themeConfig.layout);
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
this.$store.state.themeConfig.themeConfig.isCollapse = false;
this.bus.$emit('layoutMobileResize', {
getThemeConfig.value.isCollapse = false;
proxy.mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
this.bus.$emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : this.$store.state.themeConfig.themeConfig.layout,
proxy.mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
clientWidth,
});
}
},
},
distroyed() {
window.removeEventListener('resize', this.onLayoutResize);
};
// 页面加载前
onBeforeMount(() => {
onLayoutResize();
window.addEventListener('resize', onLayoutResize);
});
// 页面卸载时
onUnmounted(() => {
window.removeEventListener('resize', onLayoutResize);
});
return {
getThemeConfig,
};
},
};
</script>

View File

@ -0,0 +1,350 @@
<template>
<div v-show="isShowLockScreen">
<div class="layout-lock-screen-mask"></div>
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': isShowLoockLogin }"></div>
<div class="layout-lock-screen">
<div
class="layout-lock-screen-date"
ref="layoutLockScreenDateRef"
@mousedown="onDown"
@mousemove="onMove"
@mouseup="onEnd"
@touchstart.stop="onDown"
@touchmove.stop="onMove"
@touchend.stop="onEnd"
>
<div class="layout-lock-screen-date-box">
<div class="layout-lock-screen-date-box-time">
{{ time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ time.s }}</span>
</div>
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
</div>
<div class="layout-lock-screen-date-top">
<SvgIcon name="elementTop" />
<div class="layout-lock-screen-date-top-text">上滑解锁</div>
</div>
</div>
<transition name="el-zoom-in-center">
<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
<div class="layout-lock-screen-login-box">
<div class="layout-lock-screen-login-box-img">
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg" />
</div>
<div class="layout-lock-screen-login-box-name">Administrator</div>
<div class="layout-lock-screen-login-box-value">
<el-input
placeholder="请输入密码"
ref="layoutLockScreenInputRef"
v-model="lockScreenPassword"
@keyup.enter.native.stop="onLockScreenSubmit()"
>
<template #append>
<el-button @click="onLockScreenSubmit">
<el-icon class="el-input__icon">
<elementRight />
</el-icon>
</el-button>
</template>
</el-input>
</div>
</div>
<div class="layout-lock-screen-login-icon">
<SvgIcon name="elementMicrophone" />
<SvgIcon name="elementAlarmClock" />
<SvgIcon name="elementSwitchButton" />
</div>
</div>
</transition>
</div>
</div>
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
export default defineComponent({
name: 'layoutLockScreen',
setup() {
const { proxy } = getCurrentInstance() as any;
const layoutLockScreenInputRef = ref();
const store = useStore();
const state: any = reactive({
transparency: 1,
downClientY: 0,
moveDifference: 0,
isShowLoockLogin: false,
isFlags: false,
querySelectorEl: '',
time: {
hm: '',
s: '',
mdq: '',
},
setIntervalTime: 0,
isShowLockScreen: false,
isShowLockScreenIntervalTime: 0,
lockScreenPassword: '',
});
// 鼠标按下
const onDown = (down: any) => {
state.isFlags = true;
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
};
// 鼠标移动
const onMove = (move: any) => {
if (state.isFlags) {
const el = state.querySelectorEl;
const opacitys = (state.transparency -= 1 / 200);
if (move.touches) {
state.moveDifference = move.touches[0].clientY - state.downClientY;
} else {
state.moveDifference = move.clientY - state.downClientY;
}
if (state.moveDifference >= 0) return false;
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
if (state.moveDifference < -400) {
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
state.moveDifference = -el.clientHeight;
setTimeout(() => {
el && el.parentNode?.removeChild(el);
}, 300);
}
if (state.moveDifference === -el.clientHeight) {
state.isShowLoockLogin = true;
layoutLockScreenInputRef.value.focus();
}
}
};
// 鼠标松开
const onEnd = () => {
state.isFlags = false;
state.transparency = 1;
if (state.moveDifference >= -400) {
state.querySelectorEl.setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
}
};
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
});
};
// 时间初始化
const initTime = () => {
state.time.hm = formatDate(new Date(), 'HH:MM');
state.time.s = formatDate(new Date(), 'SS');
state.time.mdq = formatDate(new Date(), 'mm月dd日WWW');
};
// 时间初始化定时器
const initSetTime = () => {
initTime();
state.setIntervalTime = window.setInterval(() => {
initTime();
}, 1000);
};
// 锁屏时间定时器
const initLockScreen = () => {
if (store.state.themeConfig.themeConfig.isLockScreen) {
state.isShowLockScreenIntervalTime = window.setInterval(() => {
if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
state.isShowLockScreen = true;
setLocalThemeConfig();
return false;
}
store.state.themeConfig.themeConfig.lockScreenTime--;
}, 1000);
} else {
clearInterval(state.isShowLockScreenIntervalTime);
}
};
// 存储布局配置
const setLocalThemeConfig = () => {
store.state.themeConfig.themeConfig.isDrawer = false;
Local.set('themeConfig', store.state.themeConfig.themeConfig);
};
// 密码输入点击事件
const onLockScreenSubmit = () => {
store.state.themeConfig.themeConfig.isLockScreen = false;
store.state.themeConfig.themeConfig.lockScreenTime = 30;
setLocalThemeConfig();
};
// 页面加载时
onMounted(() => {
initGetElement();
initSetTime();
initLockScreen();
});
// 页面卸载时
onUnmounted(() => {
window.clearInterval(state.setIntervalTime);
window.clearInterval(state.isShowLockScreenIntervalTime);
});
return {
layoutLockScreenInputRef,
onDown,
onMove,
onEnd,
onLockScreenSubmit,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-lock-screen-fixed {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.layout-lock-screen-filter {
filter: blur(1px);
}
.layout-lock-screen-mask {
background: var(--el-color-white);
@extend .layout-lock-screen-fixed;
z-index: 9999990;
}
.layout-lock-screen-img {
@extend .layout-lock-screen-fixed;
background-image: url('https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/03.jpg');
background-size: 100% 100%;
z-index: 9999991;
}
.layout-lock-screen {
@extend .layout-lock-screen-fixed;
z-index: 9999992;
&-date {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
color: var(--el-color-white);
z-index: 9999993;
user-select: none;
&-box {
position: absolute;
left: 30px;
bottom: 50px;
&-time {
font-size: 100px;
color: var(--color-whites);
}
&-info {
font-size: 40px;
color: var(--color-whites);
}
&-minutes {
font-size: 16px;
}
}
&-top {
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 100%;
border: 1px solid var(--el-border-color-light, #ebeef5);
background: rgba(255, 255, 255, 0.1);
color: var(--color-whites);
opacity: 0.8;
position: absolute;
right: 30px;
bottom: 50px;
text-align: center;
overflow: hidden;
transition: all 0.3s ease;
i {
transition: all 0.3s ease;
}
&-text {
opacity: 0;
position: absolute;
top: 150%;
font-size: 12px;
color: var(--color-whites);
left: 50%;
line-height: 1.2;
transform: translate(-50%, -50%);
transition: all 0.3s ease;
width: 35px;
}
&:hover {
border: 1px solid rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
color: var(--color-whites);
opacity: 1;
transition: all 0.3s ease;
i {
transform: translateY(-40px);
transition: all 0.3s ease;
}
.layout-lock-screen-date-top-text {
opacity: 1;
top: 50%;
transition: all 0.3s ease;
}
}
}
}
&-login {
position: relative;
z-index: 9999994;
width: 100%;
height: 100%;
left: 0;
top: 0;
display: flex;
flex-direction: column;
justify-content: center;
color: var(--color-whites);
&-box {
text-align: center;
margin: auto;
&-img {
width: 180px;
height: 180px;
margin: auto;
img {
width: 100%;
height: 100%;
border-radius: 100%;
}
}
&-name {
font-size: 26px;
margin: 15px 0 30px;
}
}
&-icon {
position: absolute;
right: 30px;
bottom: 30px;
i {
font-size: 20px;
margin-left: 15px;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
}
}
::v-deep(.el-input-group__append) {
background: var(--el-color-white);
padding: 0px 15px;
}
::v-deep(.el-input__inner) {
border-right-color: var(--el-border-color-extra-light);
&:hover {
border-color: var(--el-border-color-extra-light);
}
}
</style>

View File

@ -1,38 +1,41 @@
<template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
<img :src="logo" class="layout-logo-medium-img" />
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/logo/logo-mini.svg" class="layout-logo-medium-img" />
<span>{{ getThemeConfig.globalTitle }}</span>
</div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img :src="logo" class="layout-logo-size-img" />
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/logo/logo-mini.svg" class="layout-logo-size-img" />
</div>
</template>
<script>
<script lang="ts">
import { computed, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'layoutLogo',
data() {
return {
logo: require('@/assets/logo-mini.svg'),
};
},
computed: {
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 设置 logo 是否显示
setShowLogo() {
let { isCollapse, layout } = this.$store.state.themeConfig.themeConfig;
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
},
},
methods: {
});
// logo 点击实现菜单展开/收起
onThemeConfigChange() {
if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') return false;
this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
},
const onThemeConfigChange = () => {
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
return {
setShowLogo,
getThemeConfig,
onThemeConfigChange,
};
},
};
</script>
@ -45,20 +48,18 @@ export default {
align-items: center;
justify-content: center;
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
color: var(--prev-color-primary);
color: var(--color-primary);
font-size: 16px;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
&:hover {
span {
opacity: 0.9;
color: var(--color-primary-light-2);
}
}
&-medium-img {
width: 20px;
margin-right: 5px;
position: relative;
top: 2px;
}
}
.layout-logo-size {
@ -66,10 +67,15 @@ export default {
height: 50px;
display: flex;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
&-img {
width: 20px;
margin: auto;
animation: logoAnimation 0.3s ease-in-out;
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;
}
}
}
</style>

View File

@ -1,30 +1,36 @@
<template>
<el-container class="layout-container flex-center">
<Headers />
<Header />
<el-container class="layout-mian-height-50">
<Asides />
<Aside />
<div class="flex-center layout-backtop">
<TagsView v-if="getThemeConfig.isTagsview" />
<Mains />
<Main />
</div>
</el-container>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutClassic',
components: { Asides, Headers, Mains, TagsView },
computed: {
components: { Aside, Header, Main, TagsView },
setup() {
const store = useStore();
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
return {
getThemeConfig,
};
},
};
</script>

View File

@ -2,12 +2,12 @@
<el-container class="layout-container">
<ColumnsAside />
<div class="layout-columns-warp">
<Asides />
<el-container class="flex-center layout-backtop">
<Headers v-if="isFixedHeader" />
<el-scrollbar>
<Headers v-if="!isFixedHeader" />
<Mains />
<Aside />
<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</el-scrollbar>
</el-container>
</div>
@ -15,19 +15,24 @@
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
import ColumnsAside from '@/layout/component/columnsAside.vue';
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import ColumnsAside from '/@/layout/component/columnsAside.vue';
export default {
name: 'layoutColumns',
components: { Asides, Headers, Mains, ColumnsAside },
computed: {
// 是否开启固定 header
isFixedHeader() {
return this.$store.state.themeConfig.themeConfig.isFixedHeader;
},
components: { Aside, Header, Main, ColumnsAside },
setup() {
const store = useStore();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
return {
isFixedHeader,
};
},
};
</script>

View File

@ -1,41 +1,44 @@
<template>
<el-container class="layout-container">
<Asides />
<el-container class="flex-center layout-backtop">
<Headers v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef">
<Headers v-if="!isFixedHeader" />
<Mains />
<Aside />
<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</el-scrollbar>
</el-container>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
<script lang="ts">
import { computed, getCurrentInstance, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
export default {
name: 'layoutDefaults',
components: { Asides, Headers, Mains },
data() {
return {};
},
computed: {
// 是否开启固定 header
isFixedHeader() {
return this.$store.state.themeConfig.themeConfig.isFixedHeader;
},
},
watch: {
components: { Aside, Header, Main },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
// 监听路由的变化
$route: {
handler() {
this.$refs.layoutDefaultsScrollbarRef.wrap.scrollTop = 0;
},
deep: true,
},
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
}
);
return {
isFixedHeader,
};
},
};
</script>

View File

@ -1,16 +1,16 @@
<template>
<el-container class="layout-container flex-center layout-backtop">
<Headers />
<Mains />
<Header />
<Main />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
<script lang="ts">
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
export default {
name: 'layoutTransverse',
components: { Headers, Mains },
components: { Header, Main },
};
</script>

View File

@ -0,0 +1,133 @@
<template>
<div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
<SvgIcon
class="layout-navbars-breadcrumb-icon"
:name="getThemeConfig.isCollapse ? 'elementExpand' : 'elementFold'"
@click="onThemeConfigChange"
/>
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, getCurrentInstance, onMounted } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { useStore } from '/@/store/index';
export default {
name: 'layoutBreadcrumb',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 动态设置经典、横向布局不显示
const isShowBreadcrumb = computed(() => {
initRouteSplit(route.path);
const { layout, isBreadcrumb } = store.state.themeConfig.themeConfig;
if (layout === 'classic' || layout === 'transverse') {
return 'none';
} else {
return isBreadcrumb ? '' : 'none';
}
});
// 面包屑点击时
const onBreadcrumbClick = (v: any) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
// 处理面包屑数据
const getBreadcrumbList = (arr: Array<object>) => {
arr.map((item: any) => {
state.routeSplit.map((v: any, k: number, arrs: any) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
}
});
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path: string) => {
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
state.breadcrumbList = [store.state.routesList.routesList[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(store.state.routesList.routesList);
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
});
return {
onThemeConfigChange,
isShowBreadcrumb,
getThemeConfig,
onBreadcrumbClick,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
padding-left: 15px;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
margin-right: 15px;
color: var(--bg-topBarColor);
}
.layout-navbars-breadcrumb-span {
opacity: 0.7;
color: var(--bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
::v-deep(.el-breadcrumb__separator) {
opacity: 0.7;
color: var(--bg-topBarColor);
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div class="layout-navbars-close-full" v-if="isTagsViewCurrenFull">
<div class="layout-navbars-close-full-box" :title="$t('message.tagsView.closeFullscreen')" @click="onCloseFullscreen">
<SvgIcon name="elementClose" />
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'layoutCloseFull',
setup() {
const store = useStore();
const state: any = reactive({});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 关闭当前全屏
const onCloseFullscreen = () => {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
};
return {
isTagsViewCurrenFull,
onCloseFullscreen,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-close-full {
position: fixed;
z-index: 9999999999;
right: -30px;
top: -30px;
.layout-navbars-close-full-box {
width: 60px;
height: 60px;
border-radius: 100%;
position: relative;
cursor: pointer;
background: rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
i {
position: absolute;
left: 11px;
top: 35px;
color: #333333;
transition: all 0.3s ease;
}
&:hover {
background: rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
i {
color: var(--color-primary);
transition: all 0.3s ease;
}
}
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script lang="ts">
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '/@/layout/navBars/breadcrumb/user.vue';
import Logo from '/@/layout/logo/index.vue';
import Horizontal from '/@/layout/navMenu/horizontal.vue';
export default {
name: 'layoutBreadcrumbIndex',
components: { Breadcrumb, User, Logo, Horizontal },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const state: any = reactive({
menuList: [],
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 logo 显示/隐藏
const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
});
// 设置是否显示横向导航菜单
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
const resData = setSendClassicChildren(route.path);
proxy.mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(store.state.routesList.routesList);
}
};
// 设置了分割菜单时,删除底下 children
const delClassicChildren = (arr: Array<object>) => {
arr.map((v: any) => {
if (v.children) delete v.children;
});
return arr;
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 页面加载时
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
});
return {
getThemeConfig,
setIsShowLogo,
isLayoutTransverse,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
height: 50px;
display: flex;
align-items: center;
padding-right: 15px;
background: var(--bg-topBar);
border-bottom: 1px solid #f1f2f3;
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template #prefix>
<el-icon class="el-input__icon">
<elementSearch />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ $t(item.meta.title) }}
</div>
</template>
</el-autocomplete>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutBreadcrumbSearch',
setup() {
const layoutMenuAutocompleteRef = ref();
const { t } = useI18n();
const store = useStore();
const router = useRouter();
const state: any = reactive({
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
});
// 搜索弹窗打开
const openSearch = () => {
state.menuQuery = '';
state.isShowSearch = true;
initTageView();
nextTick(() => {
layoutMenuAutocompleteRef.value.focus();
});
};
// 搜索弹窗关闭
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString: any, cb: any) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter = (queryString: any) => {
return (restaurant: any) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
t(restaurant.meta.title).indexOf(queryString.toLowerCase()) > -1
);
};
};
// 初始化菜单数据
const initTageView = () => {
if (state.tagsViewList.length > 0) return false;
store.state.tagsViewRoutes.tagsViewRoutes.map((v: any) => {
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
});
};
// 当前菜单选中时
const onHandleSelect = (item: any) => {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();
};
// input 失去焦点时
const onSearchBlur = () => {
closeSearch();
};
return {
layoutMenuAutocompleteRef,
openSearch,
closeSearch,
menuSearch,
onHandleSelect,
onSearchBlur,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-search-dialog {
::v-deep(.el-dialog) {
box-shadow: unset !important;
border-radius: 0 !important;
background: rgba(0, 0, 0, 0.5);
}
::v-deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@ -0,0 +1,812 @@
<template>
<div class="layout-breadcrumb-seting">
<el-drawer
:title="$t('message.layout.configTitle')"
v-model="getThemeConfig.isDrawer"
direction="rtl"
destroy-on-close
size="240px"
@close="onDrawerClose"
>
<el-scrollbar class="layout-breadcrumb-seting-bar">
<!-- 全局主题 -->
<el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.primary" size="small" @change="onColorPickerChange('primary')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">success</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.success" size="small" @change="onColorPickerChange('success')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">info</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.info" size="small" @change="onColorPickerChange('info')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">warning</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.warning" size="small" @change="onColorPickerChange('warning')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">danger</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.danger" size="small" @change="onColorPickerChange('danger')"> </el-color-picker>
</div>
</div>
<!-- 菜单 / 顶栏 -->
<el-divider content-position="left">{{ $t('message.layout.twoTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoTopBar') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBar') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.menuBar" size="small" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBar') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.columnsMenuBar" size="small" @change="onBgColorPickerChange('columnsMenuBar')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoTopBarColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.topBarColor" size="small" @change="onBgColorPickerChange('topBarColor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBarColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBarColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.columnsMenuBarColor" size="small" @change="onBgColorPickerChange('columnsMenuBarColor')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt10">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsTopBarColorGradual') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTopBarColorGradual" @change="onTopBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorGradual') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isMenuBarColorGradual" @change="onMenuBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsColumnsMenuBarColorGradual') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isColumnsMenuBarColorGradual" @change="onColumnsMenuBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorHighlight') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
</div>
</div>
<!-- 界面设置 -->
<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCollapse" @change="onThemeConfigChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isUniqueOpened" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsFixedHeader') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout !== 'classic' ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsClassicSplitMenu') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isClassicSplitMenu" :disabled="getThemeConfig.layout !== 'classic'" @change="onClassicSplitMenuChange">
</el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsLockScreen') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isLockScreen" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt11">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeLockScreenTime') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input-number
v-model="getThemeConfig.lockScreenTime"
controls-position="right"
:min="1"
:max="9999"
@change="setLocalThemeConfig"
size="mini"
style="width: 90px"
>
</el-input-number>
</div>
</div>
<!-- 界面显示 -->
<el-divider content-position="left">{{ $t('message.layout.fourTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShowLogo') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
</div>
</div>
<div
class="layout-breadcrumb-seting-bar-flex mt15"
:style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }"
>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isBreadcrumb"
:disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
@change="onIsBreadcrumbChange"
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumbIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isBreadcrumbIcon" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsview') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsview" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsviewIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsviewIcon" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsCacheTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCacheTagsView" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: isMobile ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsSortableTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isSortableTagsView" :disabled="isMobile ? true : false" @change="onSortableTagsViewChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShareTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isShareTagsView" @change="onShareTagsViewChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFooter" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsGrayscale') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isGrayscale" @change="onAddFilterChange('grayscale')"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsInvert') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isInvert" @change="onAddFilterChange('invert')"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsDark') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isIsDark" @change="onAddDarkChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsWartermark') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isWartermark" @change="onWartermarkChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourWartermarkText') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input v-model="getThemeConfig.wartermarkText" size="mini" style="width: 90px" @input="onWartermarkTextInput($event)"></el-input>
</div>
</div>
<!-- 其它设置 -->
<el-divider content-position="left">{{ $t('message.layout.fiveTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveTagsStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.tagsStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="风格1" value="tags-style-one"></el-option>
<el-option label="风格2" value="tags-style-two"></el-option>
<el-option label="风格3" value="tags-style-three"></el-option>
<el-option label="风格4" value="tags-style-four"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveAnimation') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.animation" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option>
<el-option label="opacitys" value="opacitys"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="圆角" value="columns-round"></el-option>
<el-option label="卡片" value="columns-card"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15 mb27">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideLayout" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="水平" value="columns-horizontal"></el-option>
<el-option label="垂直" value="columns-vertical"></el-option>
</el-select>
</div>
</div>
<!-- 布局切换 -->
<el-divider content-position="left">{{ $t('message.layout.sixTitle') }}</el-divider>
<div class="layout-drawer-content-flex">
<!-- defaults 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixDefaults') }}</p>
</div>
</div>
</div>
<!-- classic 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixClassic') }}</p>
</div>
</div>
</div>
<!-- transverse 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixTransverse') }}</p>
</div>
</div>
</div>
<!-- columns 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('columns')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }">
<aside class="el-aside-dark" style="width: 10px"></aside>
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixColumns') }}</p>
</div>
</div>
</div>
</div>
<div class="copy-config">
<el-alert :title="$t('message.layout.tipText')" type="warning" :closable="false"> </el-alert>
<el-button size="small" class="copy-config-btn" type="primary" ref="copyConfigBtnRef" @click="onCopyConfigClick">
<el-icon>
<elementCopyDocument />
</el-icon>
{{ $t('message.layout.copyText') }}
</el-button>
<el-button size="small" class="copy-config-btn-reset" type="info" @click="onResetConfigClick">
<el-icon>
<elementRefreshRight />
</el-icon>
{{ $t('message.layout.resetText') }}
</el-button>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
import { useStore } from '/@/store/index';
import { getLightColor } from '/@/utils/theme';
import { verifyAndSpace } from '/@/utils/toolsValidate';
import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const { copyText } = commonFunction();
const state = reactive({
isMobile: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 1、全局主题
const onColorPickerChange = (color: string) => {
setPropertyFun(`--color-${color}`, getThemeConfig.value[color]);
setDispatchThemeConfig();
};
// 1、全局主题设置函数
const setPropertyFun = (color: string, targetVal: any) => {
document.documentElement.style.setProperty(color, targetVal);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10));
}
};
// 2、菜单 / 顶栏
const onBgColorPickerChange = (bg: string) => {
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏 --> 顶栏背景渐变
const onTopBarGradualChange = () => {
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
};
// 2、菜单 / 顶栏 --> 菜单背景渐变
const onMenuBarGradualChange = () => {
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
};
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
const onColumnsMenuBarGradualChange = () => {
setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
};
// 2、菜单 / 顶栏 --> 背景渐变函数
const setGraduaFun = (el: string, bool: boolean, color: string) => {
nextTick(() => {
let els = document.querySelector(el);
if (!els) return false;
if (bool) els.setAttribute('style', `background-image:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)})`);
else els.setAttribute('style', `background-image:${color}`);
setLocalThemeConfig();
});
};
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
const onMenuBarHighlightChange = () => {
nextTick(() => {
setTimeout(() => {
let elsItems = document.querySelectorAll('.el-menu-item');
let elActive = document.querySelector('.el-menu-item.is-active');
if (!elActive) return false;
if (getThemeConfig.value.isMenuBarColorHighlight) {
elsItems.forEach((el: any) => el.setAttribute('id', ``));
elActive.setAttribute('id', `add-is-active`);
Local.set('menuBarHighlightId', elActive.getAttribute('id'));
} else {
elActive.setAttribute('id', ``);
}
setLocalThemeConfig();
}, 0);
});
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
onMenuBarHighlightChange();
setDispatchThemeConfig();
};
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
proxy.mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') {
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
} else {
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle: any = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
};
// 4、界面显示 --> 深色模式
const onAddDarkChange = () => {
const body = document.documentElement as HTMLElement;
if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
else body.setAttribute('data-theme', '');
};
// 4、界面显示 --> 开启水印
const onWartermarkChange = () => {
getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
setLocalThemeConfig();
};
// 4、界面显示 --> 水印文案
const onWartermarkTextInput = (val: string) => {
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
if (getThemeConfig.value.wartermarkText === '') return false;
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
setLocalThemeConfig();
};
// 5、布局切换
const onSetLayout = (layout: string) => {
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
getThemeConfig.value.layout = layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
onMenuBarHighlightChange();
};
// 设置布局切换函数
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
getThemeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// 存储布局配置全局主题样式html根标签
const setLocalThemeConfigStyle = () => {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
// 一键复制配置
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
});
};
// 一键恢复默认
const onResetConfigClick = () => {
Local.clear();
window.location.reload();
};
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
const initSetStyle = () => {
setTimeout(() => {
// 2、菜单 / 顶栏 --> 顶栏背景渐变
onTopBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单背景渐变
onMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
onColumnsMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
onMenuBarHighlightChange();
}, 1300);
};
onMounted(() => {
nextTick(() => {
// 判断当前布局是否不相同不相同则初始化当前布局的样式防止监听窗口大小改变时布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
proxy.mittBus.on('onSignInClick', () => {
initSetStyle();
});
// 监听菜单点击,菜单字体背景高亮
proxy.mittBus.on('onMenuClick', () => {
onMenuBarHighlightChange();
});
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
proxy.mittBus.on('layoutMobileResize', (res: any) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
onMenuBarHighlightChange();
state.isMobile = other.isMobile();
});
setTimeout(() => {
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
initSetStyle();
// 灰色模式
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
// 色弱模式
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
// 深色模式
if (getThemeConfig.value.isIsDark) onAddDarkChange();
// 开启水印
onWartermarkChange();
// 语言国际化
if (Local.get('themeConfig')) proxy.$i18n.locale = Local.get('themeConfig').globalI18n;
}, 100);
});
});
onUnmounted(() => {
// 取消监听菜单点击,菜单字体背景高亮
proxy.mittBus.off('onMenuClick');
proxy.mittBus.off('onSignInClick');
proxy.mittBus.off('layoutMobileResize');
});
return {
openDrawer,
onColorPickerChange,
onBgColorPickerChange,
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onMenuBarHighlightChange,
onThemeConfigChange,
onIsFixedHeaderChange,
onIsShowLogoChange,
getThemeConfig,
onDrawerClose,
onAddFilterChange,
onAddDarkChange,
onWartermarkChange,
onWartermarkTextInput,
onSetLayout,
setLocalThemeConfig,
onClassicSplitMenuChange,
onIsBreadcrumbChange,
onSortableTagsViewChange,
onShareTagsViewChange,
onCopyConfigClick,
onResetConfigClick,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-breadcrumb-seting-bar {
height: calc(100vh - 50px);
padding: 0 15px;
::v-deep(.el-scrollbar__view) {
overflow-x: hidden !important;
}
.layout-breadcrumb-seting-bar-flex {
display: flex;
align-items: center;
&-label {
flex: 1;
color: #666666;
}
}
.layout-drawer-content-flex {
overflow: hidden;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
margin: 0 -5px;
.layout-drawer-content-item {
width: 50%;
height: 70px;
cursor: pointer;
border: 1px solid transparent;
position: relative;
padding: 5px;
.el-container {
height: 100%;
.el-aside-dark {
background-color: #b3c0d1;
}
.el-aside {
background-color: #d3dce6;
}
.el-header {
background-color: #b3c0d1;
}
.el-main {
background-color: #e9eef3;
}
}
.el-circular {
border-radius: 2px;
overflow: hidden;
border: 1px solid transparent;
transition: all 0.3s ease-in-out;
}
.drawer-layout-active {
border: 1px solid;
border-color: var(--color-primary);
}
.layout-tips-warp,
.layout-tips-warp-active {
transition: all 0.3s ease-in-out;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid;
border-color: var(--color-primary-light-4);
border-radius: 100%;
padding: 4px;
.layout-tips-box {
transition: inherit;
width: 30px;
height: 30px;
z-index: 9;
border: 1px solid;
border-color: var(--color-primary-light-4);
border-radius: 100%;
.layout-tips-txt {
transition: inherit;
position: relative;
top: 5px;
font-size: 12px;
line-height: 1;
letter-spacing: 2px;
white-space: nowrap;
color: var(--color-primary-light-4);
text-align: center;
transform: rotate(30deg);
left: -1px;
background-color: #e9eef3;
width: 32px;
height: 17px;
line-height: 17px;
}
}
}
.layout-tips-warp-active {
border: 1px solid;
border-color: var(--color-primary);
.layout-tips-box {
border: 1px solid;
border-color: var(--color-primary);
.layout-tips-txt {
color: var(--color-primary) !important;
background-color: #e9eef3 !important;
}
}
}
&:hover {
.el-circular {
transition: all 0.3s ease-in-out;
border: 1px solid;
border-color: var(--color-primary);
}
.layout-tips-warp {
transition: all 0.3s ease-in-out;
border-color: var(--color-primary);
.layout-tips-box {
transition: inherit;
border-color: var(--color-primary);
.layout-tips-txt {
transition: inherit;
color: var(--color-primary) !important;
background-color: #e9eef3 !important;
}
}
}
}
}
}
.copy-config {
margin: 10px 0;
.copy-config-btn {
width: 100%;
margin-top: 15px;
}
.copy-config-btn-reset {
width: 100%;
margin: 10px 0 0;
}
}
}
</style>

View File

@ -0,0 +1,308 @@
<template>
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="" :disabled="disabledSize === ''">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>
<el-dropdown-item command="medium" :disabled="disabledSize === 'medium'">{{ $t('message.user.dropdownMedium') }}</el-dropdown-item>
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>
<el-dropdown-item command="mini" :disabled="disabledSize === 'mini'">{{ $t('message.user.dropdownMini') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon :title="$t('message.user.title2')">
<elementSearch />
</el-icon>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" v-model:visible="isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<template #reference>
<el-badge :is-dot="true" @click="isShowUserNewsPopover = !isShowUserNewsPopover">
<el-icon :title="$t('message.user.title4')">
<elementBell />
</el-icon>
</el-badge>
</template>
<transition name="el-zoom-in-top">
<UserNews v-show="isShowUserNewsPopover" />
</transition>
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ getUserInfos.userName === '' ? 'test' : getUserInfos.userName }}
<el-icon class="el-icon--right">
<elementArrowDown />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<Search ref="searchRef" />
</div>
</template>
<script lang="ts">
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
import { useI18n } from 'vue-i18n';
import { resetRoute } from '/@/router/index';
import { useStore } from '/@/store/index';
import other from '/@/utils/other';
import { Session, Local } from '/@/utils/storage';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue';
export default {
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
setup() {
const { t } = useI18n();
const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const store = useStore();
const searchRef = ref();
const state = reactive({
isScreenfull: false,
isShowUserNewsPopover: false,
disabledI18n: 'zh-cn',
disabledSize: '',
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置分割样式
const layoutUserFlexNum = computed(() => {
let { layout, isClassicSplitMenu } = getThemeConfig.value;
let num = '';
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
else num = null;
return num;
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: t('message.user.logOutTitle'),
message: t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: t('message.user.logOutConfirm'),
cancelButtonText: t('message.user.logOutCancel'),
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(() => {
Session.clear(); // 清除缓存/token等
resetRoute(); // 删除/重置路由
router.push('/login');
setTimeout(() => {
ElMessage.success(t('message.user.logOutSuccess'));
}, 300);
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
};
// 菜单搜索点击
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 组件大小改变
const onComponentSizeChange = (size: string) => {
Local.remove('themeConfig');
getThemeConfig.value.globalComponentSize = size;
Local.set('themeConfig', getThemeConfig.value);
proxy.$ELEMENT.size = size;
initComponentSize();
window.location.reload();
};
// 语言切换
const onLanguageChange = (lang: string) => {
Local.remove('themeConfig');
getThemeConfig.value.globalI18n = lang;
Local.set('themeConfig', getThemeConfig.value);
proxy.$i18n.locale = lang;
initI18n();
other.useTitle();
};
// 设置 element plus 组件的国际化
const setI18nConfig = (locale: string) => {
proxy.mittBus.emit('getI18nConfig', proxy.$i18n.messages[locale]);
};
// 初始化言语国际化
const initI18n = () => {
switch (Local.get('themeConfig').globalI18n) {
case 'zh-cn':
state.disabledI18n = 'zh-cn';
setI18nConfig('zh-cn');
break;
case 'en':
state.disabledI18n = 'en';
setI18nConfig('en');
break;
case 'zh-tw':
state.disabledI18n = 'zh-tw';
setI18nConfig('zh-tw');
break;
}
};
// 初始化全局组件大小
const initComponentSize = () => {
switch (Local.get('themeConfig').globalComponentSize) {
case '':
state.disabledSize = '';
break;
case 'medium':
state.disabledSize = 'medium';
break;
case 'small':
state.disabledSize = 'small';
break;
case 'mini':
state.disabledSize = 'mini';
break;
}
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initI18n();
initComponentSize();
}
});
return {
getUserInfos,
onLayoutSetingClick,
onHandleCommandClick,
onScreenfullClick,
onSearchClick,
onComponentSizeChange,
onLanguageChange,
searchRef,
layoutUserFlexNum,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user {
display: flex;
align-items: center;
justify-content: flex-end;
&-link {
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
&-photo {
width: 25px;
height: 25px;
border-radius: 100%;
}
}
&-icon {
padding: 0 10px;
cursor: pointer;
color: var(--bg-topBarColor);
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
&:hover {
background: rgba(0, 0, 0, 0.04);
i {
display: inline-block;
animation: logoAnimation 0.3s ease-in-out;
}
}
}
::v-deep(.el-dropdown) {
color: var(--bg-topBarColor);
}
::v-deep(.el-badge) {
height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
::v-deep(.el-badge__content.is-fixed) {
top: 12px;
}
}
</style>

View File

@ -14,45 +14,44 @@
<div class="content-box-time">{{ v.time }}</div>
</div>
</template>
<div class="content-box-empty" v-else>
<div class="content-box-empty-margin">
<i class="el-icon-s-promotion"></i>
<div class="mt15">{{ $t('message.user.newDesc') }}</div>
</div>
</div>
<el-empty :description="$t('message.user.newDesc')" v-else></el-empty>
</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
</div>
</template>
<script>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'layoutBreadcrumbUserNews',
data() {
return {
setup() {
const state = reactive({
newsList: [
{
label: '关于版本发布的通知',
value: '基于 vue2.x + element ui正式发布时间2020年11月15日!',
time: '2020-11-15',
value: 'vue-next-admin基于 vue3 + CompositionAPI + typescript + vite + element plus正式发布时间2021年02月28日!',
time: '2020-12-08',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019欢迎小伙伴入群学习交流探讨',
time: '2020-11-15',
time: '2020-12-08',
},
],
};
},
methods: {
});
//
onAllReadClick() {
this.newsList = [];
},
const onAllReadClick = () => {
state.newsList = [];
};
//
onGoToGiteeClick() {
const onGoToGiteeClick = () => {
window.open('https://gitee.com/lyt-top/vue-next-admin');
},
};
return {
onAllReadClick,
onGoToGiteeClick,
...toRefs(state),
};
},
};
</script>
@ -61,14 +60,14 @@ export default {
.layout-navbars-breadcrumb-user-news {
.head-box {
display: flex;
border-bottom: 1px solid var(--prev-border-color-lighter);
border-bottom: 1px solid #ebeef5;
box-sizing: border-box;
color: var(--prev-color-text-primary);
color: #333333;
justify-content: space-between;
height: 35px;
align-items: center;
.head-box-btn {
color: var(--prev-color-primary);
color: var(--color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
@ -85,36 +84,25 @@ export default {
padding-bottom: 12px;
}
.content-box-msg {
color: var(--prev-color-text-secondary);
color: #999999;
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: var(--prev-color-text-secondary);
}
}
.content-box-empty {
height: 260px;
display: flex;
.content-box-empty-margin {
margin: auto;
text-align: center;
i {
font-size: 60px;
}
color: #999999;
}
}
}
.foot-box {
height: 35px;
color: var(--prev-color-primary);
color: var(--color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--prev-border-color-lighter);
border-top: 1px solid #ebeef5;
&:hover {
opacity: 1;
}

View File

@ -5,21 +5,24 @@
</div>
</template>
<script>
import BreadcrumbIndex from '@/layout/navBars/topBar/index.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
data() {
return {};
},
computed: {
// 设置是否显示 tagsView
setShowTagsView() {
let { layout, isTagsview } = this.$store.state.themeConfig.themeConfig;
setup() {
const store = useStore();
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
return layout !== 'classic' && isTagsview;
},
});
return {
setShowTagsView,
};
},
};
</script>

View File

@ -1,97 +1,118 @@
<template>
<div>
<transition name="el-zoom-in-center">
<ul
class="el-dropdown-menu el-popper el-dropdown-menu--medium custom-contextmenu"
:style="`top: ${dropdowns.y}px;left: ${dropdowns.x}px;`"
x-placement="bottom-end"
id="contextmenu"
v-show="isShow"
>
<li class="el-dropdown-menu__item" v-for="(v, k) in dropdownList" :key="k" @click="onCurrentContextmenuClick(v.id)">
<template v-if="!v.affix">
<i :class="v.icon"></i>
<transition name="el-zoom-in-center">
<div
aria-hidden="true"
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
role="tooltip"
data-popper-placement="bottom"
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
:key="Math.random()"
v-show="isShow"
>
<ul class="el-dropdown-menu">
<template v-for="(v, k) in dropdownList">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
tabindex="-1"
:key="k"
v-if="!v.affix"
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
>
<SvgIcon :name="v.icon" />
<span>{{ $t(v.txt) }}</span>
</template>
</li>
<div x-arrow class="popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
</li>
</template>
</ul>
</transition>
</div>
<div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script>
export default {
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
export default defineComponent({
name: 'layoutTagsViewContextmenu',
props: {
dropdown: {
type: Object,
},
},
data() {
return {
setup(props, { emit }) {
const state = reactive({
isShow: false,
dropdownList: [
{ id: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'el-icon-refresh-right' },
{ id: 1, txt: 'message.tagsView.close', affix: false, icon: 'el-icon-close' },
{ id: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'el-icon-circle-close' },
{ id: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'el-icon-folder-delete' },
{ contextMenuClickId: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'elementRefreshRight' },
{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'elementClose' },
{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'elementCircleClose' },
{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'elementFolderDelete' },
{
contextMenuClickId: 4,
txt: 'message.tagsView.fullscreen',
affix: false,
icon: 'iconfont icon-fullscreen',
},
],
path: {},
arrowLeft: 5,
};
},
computed: {
dropdowns() {
// 99 为 `Dropdown 下拉菜单` 的宽度
if (this.dropdown.x + 99 > document.documentElement.clientWidth) {
item: {},
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 99 - 5,
y: this.dropdown.y,
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return this.dropdown;
return props.dropdown;
}
},
},
mounted() {
// 监听页面监听进行右键菜单的关闭
document.body.addEventListener('click', this.closeContextmenu);
},
methods: {
});
// 当前项菜单点击
onCurrentContextmenuClick(id) {
this.$emit('currentContextmenuClick', { id, path: this.path });
},
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
openContextmenu(item) {
this.path = item.path;
item.meta.isAffix ? (this.dropdownList[1].affix = true) : (this.dropdownList[1].affix = false);
this.closeContextmenu();
const openContextmenu = (item: any) => {
state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
this.isShow = true;
}, 80);
},
state.isShow = true;
}, 10);
};
// 关闭右键菜单
closeContextmenu() {
this.isShow = false;
},
},
destroyed() {
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
document.body.removeEventListener('click', this.closeContextmenu);
},
// 监听下拉菜单位置
watch: {
dropdown: {
handler({ x }) {
if (x + 99 > document.documentElement.clientWidth) this.arrowLeft = 99 - (document.documentElement.clientWidth - x);
else this.arrowLeft = 10;
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
deep: true,
},
{
deep: true,
}
);
return {
dropdowns,
openContextmenu,
closeContextmenu,
onCurrentContextmenuClick,
...toRefs(state),
};
},
};
});
</script>
<style scoped lang="scss">
@ -106,5 +127,10 @@ export default {
font-size: 12px !important;
}
}
.el-dropdown-menu-arrow {
&::before {
content: '';
}
}
}
</style>

View File

@ -1,113 +1,350 @@
<template>
<div class="layout-navbars-tagsview">
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
<li
v-for="(v, k) in tagsViewList"
:key="k"
class="layout-navbars-tagsview-ul-li"
:data-name="v.name"
:class="{ 'is-active': v.path === tagsRoutePath }"
:data-url="v.url"
:class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)"
@click="onTagsClick(v, k)"
ref="tagsRefs"
:ref="
(el) => {
if (el) tagsRefs[k] = el;
}
"
>
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="v.path === tagsRoutePath"></i>
<i
class="layout-navbars-tagsview-ul-li-iconfont font14 is-tagsview-icon"
:class="v.meta.icon"
v-if="v.path !== tagsRoutePath && getThemeConfig.isTagsviewIcon"
></i>
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon" />
<span>{{ $t(v.meta.title) }}</span>
<i
class="el-icon-refresh-right layout-navbars-tagsview-ul-li-icon ml5"
v-if="v.path === tagsRoutePath"
@click.stop="refreshCurrentTagsView(v.path)"
></i>
<i class="el-icon-close layout-navbars-tagsview-ul-li-icon ml5" v-if="!v.meta.isAffix" @click.stop="closeCurrentTagsView(v.path)"></i>
<template v-if="isActive(v)">
<SvgIcon name="elementRefreshRight" class="ml5" @click.stop="refreshCurrentTagsView($route.fullPath)" />
<SvgIcon
name="elementClose"
class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
/>
</template>
<SvgIcon
name="elementClose"
class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
/>
</li>
</ul>
</el-scrollbar>
<Contextmenu :dropdown="tagsDropdown" ref="tagsContextmenu" @currentContextmenuClick="onCurrentContextmenuClick" />
<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
</div>
</template>
<script>
import Contextmenu from '@/layout/navBars/tagsView/contextmenu';
import { Session } from '@/utils/storage.js';
<script lang="ts">
import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import Sortable from 'sortablejs';
import { ElMessage } from 'element-plus';
import { useStore } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
export default {
name: 'tagsView',
name: 'layoutTagsView',
components: { Contextmenu },
data() {
return {
userInfo: {},
tagsViewList: [],
tagsDropdown: {
x: '',
y: '',
},
setup() {
const { proxy } = getCurrentInstance() as any;
const tagsRefs = ref([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
const tagsUlRef = ref();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
routeActive: '',
routePath: route.path,
dropdown: { x: '', y: '' },
tagsRefsIndex: 0,
tagsRoutePath: this.$route.path,
tagsViewList: [],
sortable: '',
tagsViewRoutesList: [],
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 动态设置 tagsView 风格样式
setTagsStyle() {
return this.$store.state.themeConfig.themeConfig.tagsStyle;
},
},
created() {
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部
this.bus.$on('onCurrentContextmenuClick', (data) => {
this.onCurrentContextmenuClick(data);
});
},
mounted() {
this.getTagsViewRoutes();
},
methods: {
// 获取路由信息
getRoutesList() {
return this.$store.state.routesList.routesList;
},
// 当前的 tagsView 项点击时
onTagsClick(v, k) {
this.tagsRoutePath = v.path;
this.tagsRefsIndex = k;
this.$router.push(v);
},
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
getTagsRefsIndex(path) {
if (this.tagsViewList.length > 0) {
this.tagsRefsIndex = this.tagsViewList.findIndex((item) => item.path === path);
// 动态设置 tagsView 风格样式
const setTagsStyle = computed(() => {
return store.state.themeConfig.themeConfig.tagsStyle;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 tagsView 高亮
const isActive = (v) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === state.routePath;
} else {
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
}
},
};
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
const addBrowserSetSession = (tagsViewList: Array<object>) => {
Session.set('tagsViewList', tagsViewList);
};
// 获取 vuex 中的 tagsViewRoutes 列表
const getTagsViewRoutes = async () => {
state.routeActive = await setTagsViewHighlight(route);
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
state.tagsViewList = [];
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
initTagsView();
};
// vuex 中获取路由信息如果是设置了固定的isAffix进行初始化显示
const initTagsView = async () => {
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
state.tagsViewList = await Session.get('tagsViewList');
} else {
await state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
await addTagsView(route.path, route);
}
// 初始化当前元素(li)的下标
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
};
// 处理可开启多标签详情单标签详情动态路由xxx/:id/:name"),普通路由处理)
const solveAddTagsView = async (path: string, to?: any) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
let current = state.tagsViewList.filter(
(v: any) =>
v.path === isDynamicPath &&
isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
);
if (current.length <= 0) {
// 防止Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
let findItem = state.tagsViewRoutesList.find((v: any) => v.path === isDynamicPath);
if (findItem.meta.isAffix) return false;
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
findItem.url = setTagsViewHighlight(findItem);
state.tagsViewList.push({ ...findItem });
addBrowserSetSession(state.tagsViewList);
}
};
// 处理单标签时,第二次的值未覆盖第一次的 tagsViewList 值Session Storage
const singleAddTagsView = (path: string, to?: any) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
state.tagsViewList.forEach((v) => {
if (
v.path === isDynamicPath &&
!isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
) {
to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
v.url = setTagsViewHighlight(v);
addBrowserSetSession(state.tagsViewList);
}
});
};
// 1、添加 tagsView未设置隐藏isHide也添加到在 tagsView 中(可开启多标签详情,单标签详情)
const addTagsView = (path: string, to?: any) => {
// 防止拿取不到路由信息
nextTick(async () => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
let item = '';
if (to && to.meta.isDynamic) {
// 动态路由xxx/:id/:name"):参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false;
item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
} else {
// 普通路由:参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === path)) return false;
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
}
if (item.meta.isLink && !item.meta.isIframe) return false;
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
else item.query = to?.query ? to?.query : route.query;
item.url = setTagsViewHighlight(item);
await state.tagsViewList.push({ ...item });
await addBrowserSetSession(state.tagsViewList);
});
};
// 2、刷新当前 tagsView
const refreshCurrentTagsView = (fullPath: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
};
// 3、关闭当前 tagsView如果是设置了固定的isAffix不可以关闭
const closeCurrentTagsView = (path: string) => {
state.tagsViewList.map((v: any, k: number, arr: any) => {
if (!v.meta.isAffix) {
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
state.tagsViewList.splice(k, 1);
setTimeout(() => {
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
// 最后一个且高亮时
if (arr[arr.length - 1].meta.isDynamic) {
// 动态路由xxx/:id/:name"
if (k !== arr.length) router.push({ name: arr[k].name, params: arr[k].params });
else router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
} else {
// 普通路由
if (k !== arr.length) router.push({ path: arr[k].path, query: arr[k].query });
else router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
}
} else {
// 非最后一个且高亮时,跳转到下一个
if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
if (arr[k].meta.isDynamic) {
// 动态路由xxx/:id/:name"
router.push({ name: arr[k].name, params: arr[k].params });
} else {
// 普通路由
router.push({ path: arr[k].path, query: arr[k].query });
}
}
}
}, 0);
}
}
});
addBrowserSetSession(state.tagsViewList);
};
// 4、关闭其它 tagsView如果是设置了固定的isAffix不进行关闭
const closeOtherTagsView = (path: string) => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
});
addTagsView(path, route);
};
// 5、关闭全部 tagsView如果是设置了固定的isAffix不进行关闭
const closeAllTagsView = () => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
state.tagsViewList.push({ ...v });
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
}
});
addBrowserSetSession(state.tagsViewList);
};
// 6、开启当前页面全屏
const openCurrenFullscreen = async (path: string) => {
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
else await router.push({ name: item.name, query: item.query });
store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
};
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
// 防止 tagsView 非当前页演示时,操作异常
const getCurrentRouteItem = (path: string, cParams: { [key: string]: any }) => {
const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
return itemRoute.find((v: any) => {
if (
v.path === path &&
isObjectValueEqual(
v.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
cParams && Object.keys(cParams ? cParams : {}).length > 0 ? cParams : null
)
) {
return v;
} else if (v.path === path && Object.keys(cParams ? cParams : {}).length <= 0) {
return v;
}
});
};
// 当前项右键菜单点击
const onCurrentContextmenuClick = async (item) => {
const cParams = item.meta.isDynamic ? item.params : item.query;
if (!getCurrentRouteItem(item.path, cParams)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数query、params' });
const { path, name, params, query, meta, url } = getCurrentRouteItem(item.path, cParams);
switch (item.contextMenuClickId) {
case 0:
// 刷新当前
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
refreshCurrentTagsView(route.fullPath);
break;
case 1:
// 关闭当前
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
break;
case 2:
// 关闭其它
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
closeOtherTagsView(path);
break;
case 3:
// 关闭全部
closeAllTagsView();
break;
case 4:
// 开启当前页面全屏
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
break;
}
};
// 右键点击时:传 x,y 坐标值到子组件中props
const onContextmenu = (v: any, e: any) => {
const { clientX, clientY } = e;
state.dropdown.x = clientX;
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// 当前的 tagsView 项点击时
const onTagsClick = (v: any, k: number) => {
state.tagsRefsIndex = k;
router.push(v);
};
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
const setTagsViewHighlight = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params || Object.keys(params).length <= 0) return v.path;
let path = '';
for (let i in params) {
path += params[i];
}
// 判断是否是动态路由xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
// 更新滚动条显示
const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update();
};
// 鼠标滚轮滚动
onHandleScroll(e) {
this.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4;
},
const onHandleScroll = (e: any) => {
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView 横向滚动
tagsViewmoveToCurrentTag() {
this.$nextTick(() => {
const tagsRefs = this.$refs.tagsRefs;
if (tagsRefs.length <= 0) return false;
const tagsViewmoveToCurrentTag = () => {
nextTick(() => {
if (tagsRefs.value.length <= 0) return false;
// 当前 li 元素
let liDom = tagsRefs[this.tagsRefsIndex];
let liDom = tagsRefs.value[state.tagsRefsIndex];
// 当前 li 元素下标
let liIndex = this.tagsRefsIndex;
let liIndex = state.tagsRefsIndex;
// 当前 ul 下 li 元素总长度
let liLength = tagsRefs.length;
let liLength = tagsRefs.value.length;
// 最前 li
let liFirst = tagsRefs[0];
let liFirst: any = tagsRefs.value[0];
// 最后 li
let liLast = tagsRefs[tagsRefs.length - 1];
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
// 当前滚动条的值
let scrollRefs = this.$refs.scrollbarRef.$refs.wrap;
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
@ -115,13 +352,13 @@ export default {
// 当前滚动条偏移距离
let scrollL = scrollRefs.scrollLeft;
// 上一个 tags li dom
let liPrevTag = tagsRefs[this.tagsRefsIndex - 1];
let liPrevTag: any = tagsRefs.value[state.tagsRefsIndex - 1];
// 下一个 tags li dom
let liNextTag = tagsRefs[this.tagsRefsIndex + 1];
let liNextTag: any = tagsRefs.value[state.tagsRefsIndex + 1];
// 上一个 tags li dom 的偏移距离
let beforePrevL = '';
let beforePrevL: any = '';
// 下一个 tags li dom 的偏移距离
let afterNextL = '';
let afterNextL: any = '';
if (liDom === liFirst) {
// 头部
scrollRefs.scrollLeft = 0;
@ -130,10 +367,10 @@ export default {
scrollRefs.scrollLeft = scrollS - offsetW;
} else {
// 非头/尾部
if (liIndex === 0) beforePrevL = liFirst?.offsetLeft - 5;
if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5;
else beforePrevL = liPrevTag?.offsetLeft - 5;
if (liIndex === liLength) afterNextL = liLast?.offsetLeft + liLast.offsetWidth + 5;
else afterNextL = liNextTag?.offsetLeft + liNextTag.offsetWidth + 5;
if (liIndex === liLength) afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5;
else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5;
if (afterNextL > scrollL + offsetW) {
scrollRefs.scrollLeft = afterNextL - offsetW;
} else if (beforePrevL < scrollL) {
@ -141,227 +378,174 @@ export default {
}
}
// 更新滚动条,防止不出现
this.updateScrollbar();
updateScrollbar();
});
},
// 更新滚动条显示
updateScrollbar() {
this.$refs.scrollbarRef.update();
},
// 递归查找当前路径下的组件信息
filterCurrentMenu(arr, currentPath, callback) {
arr.map((item) => {
if (item.path === currentPath) {
callback(item);
return false;
}
item = Object.assign({}, item);
if (item.children) {
item.children = this.filterCurrentMenu(item.children, currentPath, callback);
}
});
},
// 数组对象去重
duplicate(arr) {
let newobj = {};
arr = arr.reduce((preVal, curVal) => {
newobj[curVal.path] ? '' : (newobj[curVal.path] = preVal.push(curVal));
return preVal;
}, []);
return arr;
},
// 获取 vuex 中的 tagsViewRoutes 列表
getTagsViewRoutes() {
this.tagsRoutePath = this.$route.path;
this.tagsViewList = [];
if (!this.$store.state.themeConfig.themeConfig.isCacheTagsView) Session.remove('tagsViewList');
this.tagsViewRoutesList = this.$store.state.tagsViewRoutes.tagsViewRoutes;
this.initTagsViewList();
},
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
addBrowserSetSession(tagsViewList) {
Session.set('tagsViewList', tagsViewList);
},
// 初始化设置了 tagsView 数据
initTagsViewList() {
if (Session.get('tagsViewList') && this.$store.state.themeConfig.themeConfig.isCacheTagsView) {
this.tagsViewList = Session.get('tagsViewList');
} else {
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) this.tagsViewList.push({ ...v });
});
this.addTagsView(this.$route.path);
}
// 初始化当前元素(li)的下标
this.getTagsRefsIndex(this.$route.path);
// 添加初始化横向滚动条移动到对应位置
this.tagsViewmoveToCurrentTag();
},
// 添加 tagsView未设置隐藏isHide也添加到在 tagsView 中
addTagsView(path, to) {
if (this.tagsViewList.some((v) => v.path === path)) return false;
const item = this.tagsViewRoutesList.find((v) => v.path === path);
if (item.meta.isLink && !item.meta.isIframe) return false;
item.query = to?.query ? to?.query : this.$route.query;
this.tagsViewList.push({ ...item });
this.addBrowserSetSession(this.tagsViewList);
},
// 右键菜单点击时显示菜单列表
onContextmenu(v, e) {
let { clientX, clientY } = e;
this.tagsDropdown.x = clientX;
this.tagsDropdown.y = clientY;
this.$refs.tagsContextmenu.openContextmenu(v);
},
// 当前项右键菜单点击
onCurrentContextmenuClick(data) {
let { id, path } = data;
let currentTag = this.tagsViewList.find((v) => v.path === path);
switch (id) {
case 0:
this.refreshCurrentTagsView(path);
this.$router.push({ path, query: currentTag.query });
break;
case 1:
this.closeCurrentTagsView(path);
break;
case 2:
this.$router.push({ path, query: currentTag.query });
this.closeOtherTagsView(path);
break;
case 3:
this.closeAllTagsView(path);
break;
}
},
// 1、刷新当前 tagsView
refreshCurrentTagsView(path) {
this.bus.$emit('onTagsViewRefreshRouterView', path);
},
// 2、关闭当前 tagsView当前项 `tags-view` icon 关闭时点击如果是设置了固定的isAffix不可以关闭
closeCurrentTagsView(path) {
this.tagsViewList.map((v, k, arr) => {
if (!v.meta.isAffix) {
if (v.path === path) {
this.tagsViewList.splice(k, 1);
setTimeout(() => {
// 最后一个
if (this.tagsViewList.length === k) this.$router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
// 否则,跳转到下一个
else this.$router.push({ path: arr[k].path, query: arr[k].query });
}, 0);
};
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
const getTagsRefsIndex = (path: string) => {
nextTick(async () => {
// await 使用该写法,防止拿取不到 tagsViewList 列表数据不完整
let tagsViewList = await state.tagsViewList;
state.tagsRefsIndex = tagsViewList.findIndex((v: any) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === path;
} else {
return v.url === path;
}
});
// 添加初始化横向滚动条移动到对应位置
tagsViewmoveToCurrentTag();
});
};
// 设置 tagsView 可以进行拖拽
const initSortable = async () => {
const el = document.querySelector('.layout-navbars-tagsview-ul') as HTMLElement;
if (!el) return false;
state.sortable.el && state.sortable.destroy();
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-url',
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
onEnd: () => {
const sortEndList: any = [];
state.sortable.toArray().map((val: any) => {
state.tagsViewList.map((v: any) => {
if (v.url === val) sortEndList.push({ ...v });
});
});
addBrowserSetSession(sortEndList);
},
});
};
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
const onSortableResize = async () => {
await initSortable();
if (other.isMobile()) state.sortable.el && state.sortable.destroy();
};
// 页面加载前
onBeforeMount(() => {
// 初始化,防止手机端直接访问时还可以拖拽
onSortableResize();
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
proxy.mittBus.on('onCurrentContextmenuClick', (data: object) => {
onCurrentContextmenuClick(data);
});
// 监听布局配置界面开启/关闭拖拽
proxy.mittBus.on('openOrCloseSortable', () => {
initSortable();
});
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
proxy.mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
}
});
this.addBrowserSetSession(this.tagsViewList);
},
// 3、关闭其它 tagsView如果是设置了固定的isAffix不进行关闭
closeOtherTagsView(path) {
this.tagsViewList = [];
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) this.tagsViewList.push({ ...v });
});
this.addTagsView(path);
},
// 4、关闭全部 tagsView如果是设置了固定的isAffix不进行关闭
closeAllTagsView(path) {
this.tagsViewList = [];
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) {
this.tagsViewList.push({ ...v });
if (this.tagsViewList.some((v) => v.path === path)) this.$router.push({ path, query: this.$route.query });
else this.$router.push({ path: v.path, query: this.$route.query });
}
});
this.addBrowserSetSession(this.tagsViewList);
},
},
watch: {
// 监听路由变化
$route: {
handler(to) {
this.tagsRoutePath = to.path;
this.addTagsView(to.path, to);
this.getTagsRefsIndex(to.path);
this.tagsViewmoveToCurrentTag();
},
deep: true,
},
},
destroyed() {
// 取消非本页面调用监听fun/tagsView
this.bus.$off('onCurrentContextmenuClick');
});
// 页面卸载时
onUnmounted(() => {
// 取消非本页面调用监听
proxy.mittBus.off('onCurrentContextmenuClick');
// 取消监听布局配置界面开启/关闭拖拽
proxy.mittBus.off('openOrCloseSortable');
// 取消监听布局配置开启 TagsView 共用
proxy.mittBus.off('openShareTagsView');
// 取消窗口 resize 监听
window.removeEventListener('resize', onSortableResize);
});
// 页面更新时
onBeforeUpdate(() => {
tagsRefs.value = [];
});
// 页面加载时
onMounted(() => {
// 初始化 vuex 中的 tagsViewRoutes 列表
getTagsViewRoutes();
initSortable();
});
// 路由更新时
onBeforeRouteUpdate(async (to) => {
state.routeActive = setTagsViewHighlight(to);
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
await addTagsView(to.path, to);
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
});
// 监听路由的变化,动态赋值给 tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
});
return {
isActive,
onContextmenu,
getTagsViewRoutes,
onTagsClick,
tagsRefs,
contextmenuRef,
scrollbarRef,
tagsUlRef,
onHandleScroll,
getThemeConfig,
setTagsStyle,
refreshCurrentTagsView,
closeCurrentTagsView,
onCurrentContextmenuClick,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.layout-navbars-tagsview {
flex: 1;
background-color: var(--prev-bg-white);
border-bottom: 1px solid var(--prev-border-color-lighter);
& ::v-deep .is-vertical {
display: none !important;
background-color: var(--el-color-white);
border-bottom: 1px solid #f1f2f3;
position: relative;
z-index: 4;
::v-deep(.el-scrollbar__wrap) {
overflow-x: auto !important;
}
&-ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
height: 34px;
display: flex;
align-items: center;
white-space: nowrap;
color: var(--prev-color-text-regular);
color: var(--el-text-color-regular);
font-size: 12px;
white-space: nowrap;
padding: 0 15px;
&-li {
height: 26px;
line-height: 26px;
display: flex;
align-items: center;
border: 1px solid var(--prev-border-color-lighter);
padding: 0 12px 0 15px;
border: 1px solid #e6e6e6;
padding: 0 15px;
margin-right: 5px;
border-radius: 2px;
position: relative;
z-index: 0;
cursor: pointer;
justify-content: space-between;
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
&::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: var(--prev-color-primary);
z-index: -1;
opacity: 0;
transform: scale3d(0.7, 1, 1);
transition: transform 0.3s, opacity 0.3s;
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
&:hover {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
border-color: transparent;
&::before {
opacity: 1;
transform: translate3d(0, 0, 0);
border-radius: 2px;
}
.is-tagsview-icon {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
}
background-color: var(--color-primary-light-9);
color: var(--color-primary);
border-color: var(--color-primary-light-6);
}
&-iconfont {
position: relative;
left: -5px;
top: 1px;
color: var(--prev-color-text-white);
font-size: 12px;
}
&-icon {
border-radius: 100%;
@ -370,35 +554,102 @@ export default {
width: 14px;
text-align: center;
line-height: 14px;
top: 1px;
right: -5px;
&:hover {
color: var(--color-whites);
background-color: var(--color-primary-light-3);
}
}
.is-tagsview-icon {
color: var(--prev-color-text-regular);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
.layout-icon-active {
display: block;
}
.layout-icon-three {
display: none;
}
}
.is-active {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
border-color: transparent;
&::before {
opacity: 1;
transform: translate3d(0, 0, 0);
border-radius: 2px;
}
color: var(--color-whites);
background: var(--color-primary);
border-color: var(--color-primary);
transition: border-color 3s ease;
}
}
& ::-webkit-scrollbar {
display: none !important;
// 风格2
.tags-style-two {
.layout-navbars-tagsview-ul-li {
height: 34px !important;
line-height: 34px !important;
border: none !important;
.layout-navbars-tagsview-ul-li-iconfont {
display: none;
}
.layout-icon-active {
display: none;
}
.layout-icon-three {
display: block;
}
}
.is-active {
background: none !important;
color: var(--color-primary) !important;
border-bottom: 2px solid !important;
border-color: var(--color-primary) !important;
border-radius: 0 !important;
}
}
// // 风格2
// .tags-style-two {
// }
// // 风格3
// .tags-style-three {
// }
// // 风格4
// .tags-style-four {
// }
// 风格3
.tags-style-three {
.layout-navbars-tagsview-ul-li {
height: 34px !important;
line-height: 34px !important;
border-right: 1px solid #f6f6f6 !important;
border-top: none !important;
border-bottom: none !important;
border-left: none !important;
border-radius: 0 !important;
margin-right: 0 !important;
&:first-of-type {
border-left: 1px solid #f6f6f6 !important;
}
.layout-icon-active {
display: none;
}
.layout-icon-three {
display: block;
}
}
.is-active {
background: var(--el-color-white) !important;
color: var(--color-primary) !important;
border-top: 1px solid !important;
border-top-color: var(--color-primary) !important;
}
}
// 风格4
.tags-style-four {
.layout-navbars-tagsview-ul-li {
margin-right: 0 !important;
border: none !important;
position: relative;
border-radius: 3px !important;
.layout-icon-active {
display: none;
}
.layout-icon-three {
display: block;
}
&:hover {
background: none !important;
}
}
.is-active {
background: none !important;
color: var(--color-primary) !important;
}
}
}
.layout-navbars-tagsview-shadow {
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
}
</style>

View File

@ -1,139 +0,0 @@
<template>
<div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
<i
class="layout-navbars-breadcrumb-icon"
:class="getThemeConfig.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
@click="onThemeConfigChange"
></i>
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.path">
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ $t(v.meta.title) }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script>
import { Local } from '@/utils/storage.js';
export default {
name: 'layoutBreadcrumb',
data() {
return {
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 动态设置经典、横向布局不显示
isShowBreadcrumb() {
const { layout, isBreadcrumb } = this.$store.state.themeConfig.themeConfig;
if (layout === 'classic' || layout === 'transverse') {
return 'none';
} else {
return isBreadcrumb ? '' : 'none';
}
},
},
mounted() {
this.initRouteSplit(this.$route.path);
},
methods: {
// breadcrumb 当前项点击时
onBreadcrumbClick(v) {
const { redirect, path } = v;
if (redirect) this.$router.push(redirect);
else this.$router.push(path);
},
// breadcrumb icon 点击菜单展开与收起
onThemeConfigChange() {
this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
this.setLocalThemeConfig();
},
// 存储布局配置
setLocalThemeConfig() {
Local.remove('themeConfigPrev');
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
},
// 递归设置 breadcrumb
getBreadcrumbList(arr) {
arr.map((item) => {
this.routeSplit.map((v, k, arrs) => {
if (this.routeSplitFirst === item.path) {
this.routeSplitFirst += `/${arrs[this.routeSplitIndex]}`;
this.breadcrumbList.push(item);
this.routeSplitIndex++;
if (item.children) this.getBreadcrumbList(item.children);
}
});
});
},
// 当前路由分割处理
initRouteSplit(path) {
this.breadcrumbList = [
{
path: '/',
meta: {
title: this.$store.state.routesList.routesList[0].meta.title,
icon: this.$store.state.routesList.routesList[0].meta.icon,
},
},
];
this.routeSplit = path.split('/');
this.routeSplit.shift();
this.routeSplitFirst = `/${this.routeSplit[0]}`;
this.routeSplitIndex = 1;
this.getBreadcrumbList(this.$store.state.routesList.routesList);
},
},
// 监听路由的变化
watch: {
$route: {
handler(newVal) {
this.initRouteSplit(newVal.path);
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
padding-left: 15px;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
margin-right: 15px;
color: var(--prev-bg-topBarColor);
opacity: 0.8;
&:hover {
opacity: 1;
}
}
.layout-navbars-breadcrumb-span {
opacity: 0.7;
color: var(--prev-bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
}
</style>

View File

@ -1,77 +0,0 @@
<template>
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script>
import Breadcrumb from '@/layout/navBars/topBar/breadcrumb.vue';
import User from '@/layout/navBars/topBar/user.vue';
import Logo from '@/layout/logo/index.vue';
import Horizontal from '@/layout/navMenu/horizontal.vue';
export default {
name: 'layoutNavBars',
components: { Breadcrumb, User, Logo, Horizontal },
data() {
return {
menuList: [],
};
},
computed: {
// 设置 logo 是否显示
setIsShowLogo() {
let { isShowLogo, layout } = this.$store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
},
// 设置是否显示横向菜单
isLayoutTransverse() {
let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
},
},
mounted() {
this.setFilterRoutes();
},
methods: {
// 设置路由的过滤
setFilterRoutes() {
this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
},
// 设置路由的过滤递归函数
filterRoutesFun(arr) {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
return item;
});
},
},
watch: {
// 监听 vuex 数据变化
'$store.state': {
handler(val) {
if (val.routesList.routesList.length === this.menuList.length) return false;
this.setFilterRoutes();
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
height: 50px;
display: flex;
align-items: center;
padding-right: 15px;
overflow: hidden;
background: var(--prev-bg-topBar);
border-bottom: 1px solid var(--prev-border-color-lighter);
}
</style>

View File

@ -1,100 +0,0 @@
<template>
<div class="layout-search-dialog">
<el-dialog :visible.sync="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
prefix-icon="el-icon-search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template slot-scope="{ item }">
<div><i :class="item.meta.icon" class="mr10"></i>{{ $t(item.meta.title) }}</div>
</template>
</el-autocomplete>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'layoutBreadcrumbSearch',
data() {
return {
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
};
},
methods: {
// 搜索弹窗打开
openSearch() {
this.menuQuery = '';
this.isShowSearch = true;
this.initTageView();
this.$nextTick(() => {
this.$refs.layoutMenuAutocompleteRef.focus();
});
},
// 搜索弹窗关闭
closeSearch() {
setTimeout(() => {
this.isShowSearch = false;
}, 150);
},
// 菜单搜索数据过滤
menuSearch(queryString, cb) {
let results = queryString ? this.tagsViewList.filter(this.createFilter(queryString)) : this.tagsViewList;
cb(results);
},
// 菜单搜索过滤
createFilter(queryString) {
return (restaurant) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
this.$t(restaurant.meta.title).toLowerCase().indexOf(queryString.toLowerCase()) > -1
);
};
},
// 初始化菜单数据
initTageView() {
if (this.tagsViewList.length > 0) return false;
this.$store.state.tagsViewRoutes.tagsViewRoutes.map((v) => {
if (!v.meta.isHide) this.tagsViewList.push({ ...v });
});
},
// 当前菜单选中时
onHandleSelect(item) {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) this.$router.push(redirect);
else this.$router.push(path);
this.closeSearch();
},
// input 失去焦点时
onSearchBlur() {
this.closeSearch();
},
},
};
</script>
<style scoped lang="scss">
.layout-search-dialog {
::v-deep .el-dialog {
box-shadow: unset !important;
border-radius: 0 !important;
background: rgba(0, 0, 0, 0.5);
}
::v-deep .el-autocomplete {
width: 560px;
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@ -1,538 +0,0 @@
<template>
<div class="layout-breadcrumb-seting">
<el-drawer
:title="$t('message.layout.configTitle')"
:visible.sync="getThemeConfig.isDrawer"
direction="rtl"
destroy-on-close
size="240px"
@close="onDrawerClose"
>
<el-scrollbar class="layout-breadcrumb-seting-bar">
<!-- 全局主题 -->
<el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.primary" size="small" @change="onColorPickerChange"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsDark') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isIsDark" :width="35" @change="onAddDarkChange"></el-switch>
</div>
</div>
<!-- 界面设置 -->
<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCollapse" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isUniqueOpened" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsFixedHeader') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFixedHeader" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<!-- 界面显示 -->
<el-divider content-position="left">{{ $t('message.layout.fourTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShowLogo') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isShowLogo" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div
class="layout-breadcrumb-seting-bar-flex mt15"
:style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }"
>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isBreadcrumb"
:disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
:width="35"
@change="setLocalThemeConfig"
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumbIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isBreadcrumbIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsview') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsview" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsviewIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsviewIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsCacheTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCacheTagsView" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFooter" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsGrayscale') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isGrayscale" :width="35" @change="onAddFilterChange('grayscale')"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsInvert') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isInvert" :width="35" @change="onAddFilterChange('invert')"></el-switch>
</div>
</div>
<!-- 其它设置 -->
<el-divider content-position="left">{{ $t('message.layout.fiveTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveTagsStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.tagsStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="风格1" value="tags-style-one"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveAnimation') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.animation" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option>
<el-option label="opacitys" value="opacitys"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="圆角" value="columns-round"></el-option>
<el-option label="卡片" value="columns-card"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideLayout" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="水平" value="columns-horizontal"></el-option>
<el-option label="垂直" value="columns-vertical"></el-option>
</el-select>
</div>
</div>
<!-- 布局切换 -->
<el-divider content-position="left">{{ $t('message.layout.sixTitle') }}</el-divider>
<div class="layout-drawer-content-flex">
<!-- defaults 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixDefaults') }}</p>
</div>
</div>
</div>
<!-- classic 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixClassic') }}</p>
</div>
</div>
</div>
<!-- transverse 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixTransverse') }}</p>
</div>
</div>
</div>
<!-- columns 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('columns')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }">
<aside class="el-aside-dark" style="width: 10px"></aside>
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixColumns') }}</p>
</div>
</div>
</div>
</div>
<div class="copy-config">
<el-alert :title="$t('message.layout.tipText')" type="warning" :closable="false"> </el-alert>
<el-button
size="small"
class="copy-config-btn"
icon="el-icon-document-copy"
type="primary"
ref="copyConfigBtnRef"
@click="onCopyConfigClick"
>{{ $t('message.layout.copyText') }}
</el-button>
<el-button size="small" class="copy-config-btn-reset" type="info" icon="el-icon-refresh-right" @click="onResetConfigClick">
{{ $t('message.layout.resetText') }}
</el-button>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script>
import ClipboardJS from 'clipboard';
import { Local } from '@/utils/storage.js';
import { useChangeColor } from '@/utils/theme.js';
import config from '/package.json';
export default {
name: 'layoutBreadcrumbSeting',
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
// 判断当前布局是否不相同不相同则初始化当前布局的样式防止监听窗口大小改变时布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) this.initSetLayoutChange();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
this.bus.$on('layoutMobileResize', (res) => {
if (this.$store.state.themeConfig.themeConfig.layout === res.layout) return false;
this.$store.state.themeConfig.themeConfig.layout = res.layout;
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.$store.state.themeConfig.themeConfig.isCollapse = false;
this.initSetLayoutChange();
});
},
mounted() {
this.initLayoutConfig();
},
methods: {
// 全局主题
onColorPickerChange() {
if (!this.getThemeConfig.primary) return;
// 颜色加深
document.documentElement.style.setProperty('--prev-color-primary', this.getThemeConfig.primary);
// 颜色变浅
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(
`--prev-color-primary-light-${i}`,
`${useChangeColor().getLightColor(this.getThemeConfig.primary, i / 10)}`
);
}
this.setLocalThemeConfig();
},
// 深色模式
onAddDarkChange() {
const body = document.documentElement;
if (this.getThemeConfig.isIsDark) body.setAttribute('data-theme', 'dark');
else body.setAttribute('data-theme', '');
this.setLocalThemeConfig();
},
// 初始化:刷新页面时,设置了值,直接取缓存中的值进行初始化
initLayoutConfig() {
window.addEventListener('load', () => {
// 默认样式
this.onColorPickerChange();
// 灰色模式
if (this.$store.state.themeConfig.themeConfig.isGrayscale) this.onAddFilterChange('grayscale');
// 色弱模式
if (this.$store.state.themeConfig.themeConfig.isInvert) this.onAddFilterChange('invert');
// 深色模式
if (this.$store.state.themeConfig.themeConfig.isIsDark) this.onAddDarkChange();
// 语言国际化
if (Local.get('themeConfigPrev')) this.$i18n.locale = Local.get('themeConfigPrev').globalI18n;
});
},
// 存储布局配置
setLocalThemeConfig() {
Local.remove('themeConfigPrev');
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.setLocalThemeConfigStyle();
},
// 存储布局配置全局主题样式html根标签
setLocalThemeConfigStyle() {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
},
// 布局配置弹窗打开
openDrawer() {
this.$store.state.themeConfig.themeConfig.isDrawer = true;
},
// 关闭弹窗时,初始化变量
onDrawerClose() {
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.setLocalThemeConfig();
},
// 灰色模式/色弱模式
onAddFilterChange(attr) {
if (attr === 'grayscale') {
if (this.$store.state.themeConfig.themeConfig.isGrayscale) this.$store.state.themeConfig.themeConfig.isInvert = false;
} else {
if (this.$store.state.themeConfig.themeConfig.isInvert) this.$store.state.themeConfig.themeConfig.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale'
? `grayscale(${this.$store.state.themeConfig.themeConfig.isGrayscale ? 1 : 0})`
: `invert(${this.$store.state.themeConfig.themeConfig.isInvert ? '80%' : '0%'})`;
const appEle = document.body;
appEle.setAttribute('style', `filter: ${cssAttr};`);
this.setLocalThemeConfig();
},
// 布局切换
onSetLayout(layout) {
Local.set('oldLayout', layout);
if (this.$store.state.themeConfig.themeConfig.layout === layout) return false;
this.$store.state.themeConfig.themeConfig.layout = layout;
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.initSetLayoutChange();
},
// 设置布局切换,重置主题样式
initSetLayoutChange() {
if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
this.onBgColorPickerChange('menuBar', '#ffffff');
this.onBgColorPickerChange('menuBarColor', '#606266');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
} else if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') {
this.onBgColorPickerChange('menuBarColor', '#ffffff');
this.onBgColorPickerChange('topBar', '#545c64');
this.onBgColorPickerChange('topBarColor', '#ffffff');
} else if (this.$store.state.themeConfig.themeConfig.layout === 'columns') {
this.onBgColorPickerChange('menuBar', '#ffffff');
this.onBgColorPickerChange('menuBarColor', '#606266');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
} else {
this.onBgColorPickerChange('menuBar', '#545c64');
this.onBgColorPickerChange('menuBarColor', '#eaeaea');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
}
},
// 菜单 / 顶栏背景等
onBgColorPickerChange(bg, rgb) {
document.documentElement.style.setProperty(`--prev-bg-${bg}`, rgb);
this.setLocalThemeConfigStyle();
},
// 一键复制配置
onCopyConfigClick() {
this.$store.state.themeConfig.themeConfig.isDrawer = false;
let clipboardJS = new ClipboardJS('.copy-config-btn', {
text: () => JSON.stringify(this.$store.state.themeConfig.themeConfig),
});
clipboardJS.on('success', () => {
this.$message.success('配置复制成功');
this.isDrawer = false;
clipboardJS.destroy();
});
clipboardJS.on('error', () => {
this.$message.error('配置复制失败');
});
},
// 一键恢复默认
onResetConfigClick() {
Local.clear();
window.location.reload();
Local.set('version', config.version);
},
},
};
</script>
<style scoped lang="scss">
.layout-breadcrumb-seting-bar {
height: calc(100vh - 50px);
padding: 0 15px;
::v-deep .el-scrollbar__view {
overflow-x: hidden !important;
}
.layout-breadcrumb-seting-bar-flex {
display: flex;
align-items: center;
&-label {
flex: 1;
color: var(--prev-color-text-primary);
}
}
.layout-drawer-content-flex {
overflow: hidden;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
margin: 0 -5px;
.layout-drawer-content-item {
width: 50%;
height: 70px;
cursor: pointer;
border: 1px solid transparent;
position: relative;
padding: 5px;
.el-container {
height: 100%;
.el-aside-dark {
background-color: var(--prev-color-seting-header);
}
.el-aside {
background-color: var(--prev-color-seting-aside);
}
.el-header {
background-color: var(--prev-color-seting-header);
}
.el-main {
background-color: var(--prev-color-seting-main);
}
}
.el-circular {
border-radius: 2px;
overflow: hidden;
border: 1px solid transparent;
transition: all 0.3s ease-in-out;
}
.drawer-layout-active {
border: 1px solid;
border-color: var(--prev-color-primary);
}
.layout-tips-warp,
.layout-tips-warp-active {
transition: all 0.3s ease-in-out;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid;
border-color: var(--prev-color-primary-light-5);
border-radius: 100%;
padding: 4px;
.layout-tips-box {
transition: inherit;
width: 30px;
height: 30px;
z-index: 9;
border: 1px solid;
border-color: var(--prev-color-primary-light-5);
border-radius: 100%;
.layout-tips-txt {
transition: inherit;
position: relative;
top: 5px;
font-size: 12px;
line-height: 1;
letter-spacing: 2px;
white-space: nowrap;
color: var(--prev-color-primary-light-5);
text-align: center;
transform: rotate(30deg);
left: -1px;
background-color: var(--prev-color-seting-main);
width: 32px;
height: 17px;
line-height: 17px;
}
}
}
.layout-tips-warp-active {
border: 1px solid;
border-color: var(--prev-color-primary);
.layout-tips-box {
border: 1px solid;
border-color: var(--prev-color-primary);
.layout-tips-txt {
color: var(--prev-color-primary) !important;
background-color: var(--prev-color-seting-main) !important;
}
}
}
&:hover {
.el-circular {
transition: all 0.3s ease-in-out;
border: 1px solid;
border-color: var(--prev-color-primary);
}
.layout-tips-warp {
transition: all 0.3s ease-in-out;
border-color: var(--prev-color-primary);
.layout-tips-box {
transition: inherit;
border-color: var(--prev-color-primary);
.layout-tips-txt {
transition: inherit;
color: var(--prev-color-primary) !important;
background-color: var(--prev-color-seting-main) !important;
}
}
}
}
}
}
.copy-config {
margin: 10px 0;
.copy-config-btn {
width: 100%;
margin-top: 15px;
}
.copy-config-btn-reset {
width: 100%;
margin: 10px 0 0;
}
}
}
</style>

View File

@ -1,265 +0,0 @@
<template>
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="" :disabled="disabledSize === ''">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>
<el-dropdown-item command="medium" :disabled="disabledSize === 'medium'">{{ $t('message.user.dropdownMedium') }}</el-dropdown-item>
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>
<el-dropdown-item command="mini" :disabled="disabledSize === 'mini'">{{ $t('message.user.dropdownMini') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<i class="el-icon-search" :title="$t('message.user.title2')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click.stop="isShowUserNewsPopover = !isShowUserNewsPopover">
<el-popover placement="bottom" trigger="click" v-model="isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<el-badge :is-dot="true" slot="reference">
<i class="el-icon-bell" :title="$t('message.user.title4')"></i>
</el-badge>
<transition name="el-zoom-in-top">
<UserNews v-show="isShowUserNewsPopover" />
</transition>
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onDropdownCommand">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ getUserInfos.userName === '' ? 'test' : getUserInfos.userName }}
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<Search ref="searchRef" />
</div>
</template>
<script>
import screenfull from 'screenfull';
import { Session, Local } from '@/utils/storage.js';
import UserNews from '@/layout/navBars/topBar/userNews.vue';
import Search from '@/layout/navBars/topBar/search.vue';
export default {
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
data() {
return {
isScreenfull: false,
isShowUserNewsPopover: false,
disabledI18n: 'zh-cn',
disabledSize: '',
};
},
computed: {
// 获取用户信息
getUserInfos() {
return this.$store.state.userInfos.userInfos;
},
// 设置弹性盒子布局 flex
layoutUserFlexNum() {
let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
let num = '';
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
else num = null;
return num;
},
},
mounted() {
if (Local.get('themeConfigPrev')) {
this.initI18n();
this.initComponentSize();
}
},
methods: {
// 搜索点击
onSearchClick() {
this.$refs.searchRef.openSearch();
},
// 布局配置点击
onLayoutSetingClick() {
this.bus.$emit('openSetingsDrawer');
},
// 全屏点击
onScreenfullClick() {
if (!screenfull.isEnabled) {
this.$message.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) this.isScreenfull = true;
else this.isScreenfull = false;
});
// 监听菜单 horizontal.vue 滚动条高度更新
this.bus.$emit('updateElScrollBar');
},
// 组件大小改变
onComponentSizeChange(size) {
Local.remove('themeConfigPrev');
this.$store.state.themeConfig.themeConfig.globalComponentSize = size;
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.$ELEMENT.size = size;
this.initComponentSize();
window.location.reload();
},
// 语言切换
onLanguageChange(lang) {
Local.remove('themeConfigPrev');
this.$store.state.themeConfig.themeConfig.globalI18n = lang;
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.$i18n.locale = lang;
this.initI18n();
},
// 初始化言语国际化
initI18n() {
switch (Local.get('themeConfigPrev').globalI18n) {
case 'zh-cn':
this.disabledI18n = 'zh-cn';
break;
case 'en':
this.disabledI18n = 'en';
break;
case 'zh-tw':
this.disabledI18n = 'zh-tw';
break;
}
},
// 初始化全局组件大小
initComponentSize() {
switch (Local.get('themeConfigPrev').globalComponentSize) {
case '':
this.disabledSize = '';
break;
case 'medium':
this.disabledSize = 'medium';
break;
case 'small':
this.disabledSize = 'small';
break;
case 'mini':
this.disabledSize = 'mini';
break;
}
},
// `dropdown 下拉菜单` 当前项点击
onDropdownCommand(path) {
if (path === 'logOut') {
setTimeout(() => {
this.$msgbox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: this.$t('message.user.logOutTitle'),
message: this.$t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: this.$t('message.user.logOutConfirm'),
cancelButtonText: this.$t('message.user.logOutCancel'),
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = this.$t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(() => {
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
})
.catch(() => {});
}, 150);
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
this.$router.push(path);
}
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user {
display: flex;
align-items: center;
justify-content: flex-end;
&-link {
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
&-photo {
width: 25px;
height: 25px;
border-radius: 100%;
}
}
&-icon {
padding: 0 10px;
cursor: pointer;
color: var(--prev-bg-topBarColor);
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
&:hover {
background: var(--prev-color-hover);
i {
display: inline-block;
animation: logoAnimation 0.3s ease-in-out;
}
}
}
& ::v-deep .el-dropdown {
color: var(--prev-bg-topBarColor);
}
& ::v-deep .el-badge {
height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
& ::v-deep .el-badge__content.is-fixed {
top: 12px;
}
}
</style>

View File

@ -1,38 +1,39 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
<template v-for="val in menuList">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template slot="title">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template slot="title" v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</template>
<template slot="title" v-else>
<a :href="val.meta.isLink" target="_blank">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</a>
</template>
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import SubItem from '@/layout/navMenu/subItem.vue';
export default {
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuHorizontal',
components: { SubItem },
props: {
@ -41,44 +42,45 @@ export default {
default: () => [],
},
},
data() {
return {
setup(props) {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state = reactive({
defaultActive: null,
};
},
mounted() {
this.initElMenuOffsetLeft();
this.setCurrentRouterHighlight(this.$route.path);
},
methods: {
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 设置横向滚动条可以鼠标滚轮滚动
onElMenuHorizontalScroll(e) {
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
},
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
initElMenuOffsetLeft() {
this.$nextTick(() => {
let els = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
});
},
};
// 路由过滤递归函数
filterRoutesFun(arr) {
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
},
};
// 传送当前子级数据到菜单中
setSendClassicChildren(path) {
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData = {};
this.filterRoutesFun(this.$store.state.routesList.routesList).map((v, k) => {
let currentData: any = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
@ -87,31 +89,44 @@ export default {
}
});
return currentData;
},
// 菜单激活回调
onHorizontalSelect(path) {
this.bus.$emit('setSendClassicChildren', this.setSendClassicChildren(path));
},
};
// 设置页面当前路由高亮
setCurrentRouterHighlight(path) {
const currentPathSplit = path.split('/');
if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
this.defaultActive = `/${currentPathSplit[1]}`;
const setCurrentRouterHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
if (store.state.themeConfig.themeConfig.layout === 'classic') {
state.defaultActive = `/${path.split('/')[1]}`;
} else {
this.defaultActive = path;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
},
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to);
proxy.mittBus.emit('onMenuClick');
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
return {
menuLists,
onElMenuHorizontalScroll,
...toRefs(state),
};
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.setCurrentRouterHighlight(to.path);
},
deep: true,
},
},
};
});
</script>
<style scoped lang="scss">
@ -119,13 +134,10 @@ export default {
flex: 1;
overflow: hidden;
margin-right: 30px;
::v-deep .el-scrollbar__bar.is-vertical {
::v-deep(.el-scrollbar__bar.is-vertical) {
display: none;
}
::v-deep .el-scrollbar__wrap {
overflow-y: hidden !important;
}
::v-deep a {
::v-deep(a) {
width: 100%;
}
.el-menu.el-menu--horizontal {

View File

@ -1,41 +1,45 @@
<template>
<div>
<template v-for="val in chil">
<el-submenu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template slot="title">
<i :class="val.meta.icon"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<sub-item :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
<template v-for="val in chils">
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
</template>
</div>
<sub-item :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</template>
<script>
export default {
name: 'subItem',
<script lang="ts">
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'navMenuSubItem',
props: {
chil: {
type: Array,
default() {
return [];
},
default: () => [],
},
},
};
setup(props) {
// 获取父级菜单数据
const chils = computed(() => {
return props.chil;
});
return {
chils,
};
},
});
</script>

View File

@ -1,73 +1,98 @@
<template>
<el-menu
router
background-color="transparent"
:default-active="defaultActive"
:collapse="setIsCollapse"
background-color="transparent"
:collapse="isCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
<template v-for="val in menuList">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template slot="title">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<template slot="title" v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<span>{{ $t(val.meta.title) }}</span>
</template>
<template slot="title" v-else>
<a :href="val.meta.isLink" target="_blank">{{ $t(val.meta.title) }}</a>
</template>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<SvgIcon :name="val.meta.icon" />
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener">{{ $t(val.meta.title) }}</a>
</template>
</el-menu-item>
</template>
</el-menu>
</template>
<script>
import SubItem from '@/layout/navMenu/subItem.vue';
export default {
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuVertical',
components: { SubItem },
props: {
menuList: {
type: Array,
default() {
return [];
},
default: () => [],
},
},
data() {
setup(props) {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const state = reactive({
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 设置菜单的收起/展开
watch(
store.state.themeConfig.themeConfig,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = getThemeConfig.value.isCollapse);
},
{
immediate: true,
}
);
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
proxy.mittBus.emit('onMenuClick');
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
});
return {
defaultActive: this.$route.path,
menuLists,
getThemeConfig,
...toRefs(state),
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 设置左侧菜单是否展开/收起
setIsCollapse() {
return document.body.clientWidth < 1000 ? false : this.$store.state.themeConfig.themeConfig.isCollapse;
},
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.defaultActive = to.path;
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) this.$store.state.themeConfig.themeConfig.isCollapse = false;
},
deep: true,
},
},
};
});
</script>

View File

@ -1,46 +1,60 @@
<template>
<div>
<div class="layout-view-bg-white flex h100" v-loading="iframeLoading">
<iframe :src="meta.isLink" frameborder="0" height="100%" width="100%" id="iframe"></iframe>
</div>
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" id="iframe" v-show="!iframeLoading"></iframe>
</div>
</template>
<script>
export default {
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutIfameView',
props: {
meta: {
type: Object,
default: () => {},
},
},
data() {
return {
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({
iframeLoading: true,
};
},
created() {
this.bus.$on('onTagsViewRefreshRouterView', (path) => {
if (this.$route.path !== path) return false;
this.$emit('getCurrentRouteMeta');
iframeUrl: '',
});
},
mounted() {
this.initIframeLoad();
},
methods: {
// 初始化页面加载 loading
initIframeLoad() {
this.$nextTick(() => {
this.iframeLoading = true;
const initIframeLoad = () => {
state.iframeUrl = route.meta.isLink;
nextTick(() => {
state.iframeLoading = true;
const iframe = document.getElementById('iframe');
if (!iframe) return false;
iframe.onload = () => {
this.iframeLoading = false;
state.iframeLoading = false;
};
});
},
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `1px`;
} else {
if (isTagsview) return `85px`;
else return `51px`;
}
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
watch(
() => route.path,
() => {
initIframeLoad();
}
);
return {
setIframeHeight,
...toRefs(state),
};
},
};
});
</script>

View File

@ -1,83 +1,43 @@
<template>
<div class="layout-scrollbar layout-link-container">
<div class="layout-view-bg-white flex layout-view-link">
<div class="layout-link-warp">
<i class="layout-link-icon iconfont icon-xingqiu"></i>
<div class="layout-link-msg">页面 "{{ $t(meta.title) }}" 已在新窗口中打开</div>
<el-button class="mt30" round size="small" @click="onGotoFullPage">
<i class="iconfont icon-lianjie"></i>
<span>立即前往体验</span>
</el-button>
</div>
</div>
<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin"
>{{ $t(currentRouteMeta.title) }}{{ currentRouteMeta.isLink }}</a
>
</div>
</template>
<script>
import { verifyUrl } from '@/utils/toolsValidate';
export default {
<script lang="ts">
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutLinkView',
props: {
meta: {
type: Object,
default: () => {},
},
},
methods: {
// 立即前往
onGotoFullPage() {
const { origin, pathname } = window.location;
if (verifyUrl(this.meta.isLink)) window.open(this.meta.isLink);
else window.open(`${origin}${pathname}#${this.meta.isLink}`);
},
},
};
</script>
<style scoped lang="scss">
.layout-link-container {
.layout-link-warp {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i.layout-link-icon {
position: relative;
font-size: 100px;
color: var(--prev-color-primary);
&::after {
content: '';
position: absolute;
left: 50px;
top: 0;
width: 15px;
height: 100px;
background: linear-gradient(
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(235, 255, 255, 0.5),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01)
);
transform: rotate(-15deg);
animation: toRight 5s linear infinite;
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({
currentRouteMeta: {},
});
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return `114px`;
else return `80px`;
});
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
},
{
immediate: true,
}
}
.layout-link-msg {
font-size: 12px;
color: var(--prev-bg-topBarColor);
opacity: 0.7;
margin-top: 15px;
}
}
}
</style>
);
return {
setLinkHeight,
...toRefs(state),
};
},
});
</script>

View File

@ -1,47 +1,71 @@
<template>
<div class="h100">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="keepAliveNameList">
<router-view :key="refreshRouterViewKey" />
</keep-alive>
</transition>
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="keepAliveNameList">
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
<script>
export default {
name: 'parent',
data() {
return {
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutParentView',
setup() {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state: any = reactive({
refreshRouterViewKey: null,
keepAliveNameList: [],
keepAliveNameNewList: [],
};
},
created() {
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
return store.state.themeConfig.themeConfig.animation;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
return store.state.keepAliveNames.keepAliveNames;
});
// 页面加载前,处理缓存,页面刷新时路由缓存处理
this.keepAliveNameList = this.getKeepAliveNames();
this.bus.$on('onTagsViewRefreshRouterView', (path) => {
if (this.$route.path !== path) return false;
this.keepAliveNameList = this.getKeepAliveNames().filter((name) => this.$route.name !== name);
this.refreshRouterViewKey = this.$route.path;
this.$nextTick(() => {
this.refreshRouterViewKey = null;
this.keepAliveNameList = this.getKeepAliveNames();
onBeforeMount(() => {
state.keepAliveNameList = getKeepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = null;
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.keepAliveNameList = getKeepAliveNames.value;
});
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView');
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = route.fullPath;
}
);
return {
getThemeConfig,
getKeepAliveNames,
setTransitionName,
...toRefs(state),
};
},
computed: {
// 设置主界面切换动画
setTransitionName() {
return this.$store.state.themeConfig.themeConfig.animation;
},
},
methods: {
// 获取路由缓存列表name默认路由全部缓存
getKeepAliveNames() {
return this.$store.state.keepAliveNames.keepAliveNames;
},
},
};
});
</script>

View File

@ -1,113 +0,0 @@
<template>
<div class="sponsors-container" title="点击前往体验" v-show="sponsors.isShow" @click="onSponsorsClick">
<el-carousel height="240px" indicator-position="none" :arrow="setCarouselShow" @change="onCarouselChange">
<el-carousel-item v-for="(v, k) in sponsors.list" :key="k">
<img :src="v.url" class="sponsors-img" />
<div class="sponsors-text" v-html="v.text"></div>
</el-carousel-item>
</el-carousel>
<div class="sponsors-close" title="关闭赞助商" @click.stop="onCloseSponsors">
<i class="el-icon-close"></i>
</div>
</div>
</template>
<script>
import sponsorsOne from '@/assets/ccflowRightNextAdmin.png';
export default {
name: 'layoutSponsors',
data() {
return {
sponsors: {
list: [
{
url: sponsorsOne,
text: `驰骋BPM系统包含表单引擎+流程引擎+权限控制,方便集成,配置灵活,功能强大,适合中国国情的工作流引擎.演示:http://demo.ccflow.org。右上角点star方可加群: 1060674395`,
link: 'http://www.ccflow.org/',
},
],
isShow: false,
index: 0,
},
};
},
computed: {
// 设置轮播图箭头显示
setCarouselShow() {
return this.sponsors.list.length <= 1 ? 'never' : 'hover';
},
},
methods: {
// 关闭赞助商
onCloseSponsors() {
this.sponsors.isShow = false;
},
// 轮播图改变时
onCarouselChange(e) {
this.sponsors.index = e;
},
// 当前项内容点击
onSponsorsClick() {
window.open(this.sponsors.list[this.sponsors.index].link);
},
// 延迟显示,防止影响其它界面加载
delayShow() {
setTimeout(() => {
this.sponsors.isShow = true;
}, 3000);
},
},
mounted() {
this.delayShow();
},
};
</script>
<style scoped lang="scss">
.sponsors-container {
position: fixed;
right: 15px;
bottom: 15px;
z-index: 3;
width: 200px;
background-color: var(--prev-bg-main-color);
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.12);
border-radius: 5px;
overflow: hidden;
cursor: pointer;
.sponsors-img {
width: 100%;
height: 80px;
}
.sponsors-text {
padding: 10px;
color: var(--prev-color-text-regular);
font-size: 14px;
}
.sponsors-close {
width: 60px;
height: 60px;
border-radius: 100%;
background: rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
position: absolute;
right: -35px;
bottom: -35px;
z-index: 5;
i {
position: absolute;
left: 9px;
top: 9px;
color: #afafaf;
transition: all 0.3s ease;
}
&:hover {
transition: all 0.3s ease;
i {
color: var(--prev-color-primary);
transition: all 0.3s ease;
}
}
}
}
</style>

View File

@ -1,149 +0,0 @@
<template>
<div class="upgrade-dialog">
<el-dialog
:visible.sync="isUpgrade"
width="300px"
destroy-on-close
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="upgrade-title">
<div class="upgrade-title-warp">
<span class="upgrade-title-warp-txt">{{ $t('message.upgrade.title') }}</span>
<span class="upgrade-title-warp-version">v{{ version }}</span>
</div>
</div>
<div class="upgrade-content">
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
<div class="mt5">
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/vue-prev-admin/CHANGELOG.md" target="_black">
CHANGELOG.md
</el-link>
</div>
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
</div>
<div class="upgrade-btn">
<el-button round size="small" @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="small" @click="onUpgrade" :loading="isLoading">{{ btnTxt }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { Local } from '@/utils/storage';
import config from '/package.json';
export default {
data() {
return {
isUpgrade: false,
version: config.version,
isLoading: false,
btnTxt: '',
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
methods: {
// 残忍拒绝
onCancel() {
this.isUpgrade = false;
},
// 马上更新
onUpgrade() {
this.isLoading = true;
this.btnTxt = this.$t('message.upgrade.btnTwoLoading');
setTimeout(() => {
Local.clear();
window.location.reload();
Local.set('version', this.version);
}, 2000);
},
// 延迟显示,防止刷新时界面显示太快
delayShow() {
setTimeout(() => {
this.isUpgrade = true;
}, 2000);
},
},
mounted() {
this.delayShow();
setTimeout(() => {
this.btnTxt = this.$t('message.upgrade.btnTwo');
}, 200);
},
};
</script>
<style scoped lang="scss">
.upgrade-dialog {
& ::v-deep .el-dialog {
.el-dialog__body {
padding: 0 !important;
}
.el-dialog__header {
display: none !important;
}
.upgrade-title {
text-align: center;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::after {
content: '';
position: absolute;
background-color: var(--prev-color-primary-light-1);
width: 130%;
height: 130px;
border-bottom-left-radius: 100%;
border-bottom-right-radius: 100%;
}
.upgrade-title-warp {
z-index: 1;
position: relative;
.upgrade-title-warp-txt {
color: var(--prev-color-text-white);
font-size: 22px;
letter-spacing: 3px;
}
.upgrade-title-warp-version {
background-color: var(--prev-color-primary-light-4);
color: var(--prev-color-text-white);
font-size: 12px;
position: absolute;
display: flex;
top: -2px;
right: -50px;
padding: 2px 4px;
border-radius: 2px;
}
}
}
.upgrade-content {
padding: 20px;
line-height: 22px;
color: var(--prev-color-text-regular);
.upgrade-content-desc {
color: var(--prev-color-text-placeholder);
font-size: 12px;
}
}
.upgrade-btn {
border-top: 1px solid var(--prev-border-color-lighter);
display: flex;
justify-content: space-around;
padding: 15px 20px;
.el-button {
width: 100%;
}
}
}
}
</style>

View File

@ -1,24 +0,0 @@
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import Particles from 'vue-particles';
import Element from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import '@/theme/index.scss';
import { i18n } from '@/i18n/index.js';
import { globalComponentSize } from '@/utils/componentSize.js';
Vue.use(Particles);
Vue.use(Element, { i18n: (key, value) => i18n.t(key, value), size: globalComponentSize });
Vue.config.productionTip = false;
Vue.prototype.bus = new Vue();
new Vue({
router,
store,
i18n,
render: (h) => h(App),
}).$mount('#app');

30
src/main.ts Normal file
View File

@ -0,0 +1,30 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { store, key } from './store';
import { directive } from '/@/utils/directive';
import { i18n } from '/@/i18n/index';
import other from '/@/utils/other';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss';
import mitt from 'mitt';
import screenShort from 'vue-web-screen-shot';
import VueGridLayout from 'vue-grid-layout';
const app = createApp(App);
directive(app);
other.elSvg(app);
app
.use(router)
.use(store, key)
.use(ElementPlus, { i18n: i18n.global.t, size: other.globalComponentSize })
.use(i18n)
.use(screenShort, { enableWebRtc: false })
.use(VueGridLayout)
.mount('#app');
app.config.globalProperties.mittBus = mitt();

File diff suppressed because one or more lines are too long

100
src/router/backEnd.ts Normal file
View File

@ -0,0 +1,100 @@
import { store } from '/@/store/index.ts';
import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading';
import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
import { dynamicRoutes } from '/@/router/route';
import { getMenuAdmin, getMenuTest } from '/@/api/menu/index';
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
/**
* 获取目录下的 .vue、.tsx 全部文件
* @method import.meta.glob
* @link 参考https://cn.vitejs.dev/guide/features.html#json
*/
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
/**
* 后端控制路由:初始化方法,防止刷新时路由丢失
* @method NextLoading 界面 loading 动画开始执行
* @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
* @method store.dispatch('requestOldRoutes/setBackEndControlRoutes') 存储接口原始路由未处理component根据需求选择使用
* @method setAddRoute 添加动态路由
* @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
*/
export async function initBackEndControlRoutes() {
// 界面 loading 动画开始执行
if (window.nextLoading === undefined) NextLoading.start();
// 无 token 停止执行下一步
if (!Session.get('token')) return false;
// 触发初始化用户信息
store.dispatch('userInfos/setUserInfos');
// 获取路由菜单数据
const res = await getBackEndControlRoutes();
// 存储接口原始路由未处理component根据需求选择使用
store.dispatch('requestOldRoutes/setBackEndControlRoutes', JSON.parse(JSON.stringify(res.data)));
// 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由
dynamicRoutes[0].children = await backEndComponent(res.data);
// 添加动态路由
await setAddRoute();
// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
setFilterMenuAndCacheTagsViewRoutes();
}
/**
* 请求后端路由菜单接口
* @description isRequestRoutes 为 true则开启后端控制路由
* @returns 返回后端路由菜单数据
*/
export function getBackEndControlRoutes() {
// 模拟 admin 与 test
const auth = store.state.userInfos.userInfos.roles[0];
// 管理员 admin
if (auth === 'admin') return getMenuAdmin();
// 其它用户 test
else return getMenuTest();
}
/**
* 重新请求后端路由菜单接口
* @description 用于菜单管理界面刷新菜单(未进行测试)
* @description 路径:/src/views/system/menu/component/addMenu.vue
*/
export function setBackEndControlRefreshRoutes() {
getBackEndControlRoutes();
}
/**
* 后端路由 component 转换
* @param routes 后端返回的路由表数组
* @returns 返回处理成函数后的 component
*/
export function backEndComponent(routes: any) {
if (!routes) return;
return routes.map((item: any) => {
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
item.children && backEndComponent(item.children);
return item;
});
}
/**
* 后端路由 component 转换函数
* @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件
* @param component 当前要处理项 component
* @returns 返回处理成函数后的 component
*/
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace(/..\/views|../, '');
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
}
if (matchKeys?.length > 1) {
return false;
}
}

24
src/router/frontEnd.ts Normal file
View File

@ -0,0 +1,24 @@
import { store } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading';
import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
/**
* 前端控制路由:初始化方法,防止刷新时路由丢失
* @method NextLoading 界面 loading 动画开始执行
* @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
* @method setAddRoute 添加动态路由
* @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
*/
export async function initFrontEndControlRoutes() {
// 界面 loading 动画开始执行
if (window.nextLoading === undefined) NextLoading.start();
// 无 token 停止执行下一步
if (!Session.get('token')) return false;
// 触发初始化用户信息
store.dispatch('userInfos/setUserInfos');
// 添加动态路由
await setAddRoute();
// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
setFilterMenuAndCacheTagsViewRoutes();
}

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