96 Commits

Author SHA1 Message Date
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
162 changed files with 17100 additions and 5039 deletions

4
.env
View File

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

View File

@ -2,4 +2,4 @@
ENV = 'development'
# 本地环境接口地址
VITE_API_URL = 'http://localhost:10000/'
VITE_API_URL = 'http://localhost:8888/'

View File

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

View File

@ -11,7 +11,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: ['plugin:vue/essential'],
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
rules: {
// http://eslint.cn/docs/rules/
@ -42,6 +42,12 @@ module.exports = {
'vue/no-template-key': 'off',
'vue/no-v-html': 'off',
'vue/comment-directive': 'off',
'vue/no-parsing-error': 'off',
'vue/no-deprecated-v-on-native-modifier': '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',
@ -50,5 +56,7 @@ module.exports = {
'no-multiple-template-root': 'off',
'no-unused-vars': 'error',
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
},
};

27
.gitignore vendored
View File

@ -1,6 +1,23 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.vscode
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

182
CHANGELOG.md Normal file
View File

@ -0,0 +1,182 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 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.07.02`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 图标选择器设置宽度、v-model 等问题
- 🎯 优化 滚动通知栏在手机上的体验
- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
- 🎯 优化 字体图标(自动载入) 逻辑
- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
## 1.0.8
`2021.06.29`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 表单中英文切换演示
- 🎯 优化 登录页查看密码 icon 图标
- 🎯 优化 图标选择器
- 🎯 优化 拖动指令
- 🐞 修复 form 表单在页面小于 576px 时的排版问题
## 1.0.7
`2021.06.24`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 拖动指令及其演示界面
- 🎯 优化 锁屏界面,解锁提示
- 🎯 优化 登录页在手机上显示的效果
## 1.0.6
`2021.06.23`
- 🎯 优化 去掉内嵌 iframe 内边距padding
- 🎯 优化 城市多级联动组件
- 🎯 优化 Tree 树形控件改成表格组件
- 🐞 修复 Cascader 级联选择器高度问题
## 1.0.5
`2021.06.22`
- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
- 🐞 修复 开启后端控制路由isRequestRoutes = true内嵌 iframe、外链不可使用的问题
## 1.0.4
`2021.06.19`
- 🌟 更新 依赖更新最新版本("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@芭芭拉
- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题

View File

@ -10,44 +10,38 @@
<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://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="license">
</a>
<a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="vite">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a>
<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>
#### 介绍
#### 🌈 介绍
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### 线上预览
#### ⛱️ 线上预览
###### vue3.x 版本
- 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>
- 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 版本
- pro 版本预览 <a href="http://lyt-top.gitee.io/vue-admin-wonderful-preview" target="_blank">http://lyt-top.gitee.io/vue-admin-wonderful-preview</a>
- fashion 版本预览 <a href="http://lyt-top.gitee.io/vue-admin-wonderful-fashion-preview" target="_blank">http://lyt-top.gitee.io/vue-admin-wonderful-fashion-preview</a>
- classic 版本预览 <a href="http://lyt-top.gitee.io/vue-admin-wonderful-classic-preview" target="_blank">http://lyt-top.gitee.io/vue-admin-wonderful-classic-preview</a>
- elegant 版本预览 <a href="http://lyt-top.gitee.io/vue-admin-wonderful-elegant-preview" target="_blank">http://lyt-top.gitee.io/vue-admin-wonderful-elegant-preview</a>
- strange 版本预览 <a href="http://lyt-top.gitee.io/vue-admin-wonderful-strange-preview" target="_blank">http://lyt-top.gitee.io/vue-admin-wonderful-strange-preview</a>
#### 代码仓库
#### 💒 代码仓库
- vue3.x 版本 <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">https://gitee.com/lyt-top/vue-next-admin</a>
- vue2.x 版本 <a href="https://gitee.com/lyt-top/vue-admin-wonderful" target="_blank">https://gitee.com/lyt-top/vue-admin-wonderful</a>
- vue2.x 版本 <a href="https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin" target="_blank">https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin</a>
#### 安装 cnpm
#### 🚧 安装 cnpm、yarn
- 复制代码(桌面 cmd 运行) `npm install -g cnpm --registry=https://registry.npm.taobao.org`
- 复制代码(桌面 cmd 运行) `npm install -g yarn`
#### 使用说明(vue3.x 版本)
#### 使用说明
建议使用 cnpm因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a>
```bash
# 克隆项目
@ -66,34 +60,31 @@ cnpm run dev
cnpm run build
```
#### 使用说明(vue2.x 版本)
#### 🍉 git 命令
```bash
# 克隆项目
git clone https://gitee.com/lyt-top/vue-admin-wonderful.git
- 在本地新建一个分支:`git branch newBranch`
- 切换到你的新分支:`git checkout newBranch`
- 将新分支发布在 github、gitee 上:`git push origin newBranch`
- 在本地删除一个分支:`git branch -d newBranch`
- 在 github 远程端删除一个分支:`git push origin :newBranch (分支名前的冒号代表删除)`
- 注意删除远程分支后,如果有对应的本地分支,本地分支并不会同步删除!
# 进入项目
cd vue-admin-wonderful
#### 💯 学习交流加 QQ 群
# 安装依赖
cnpm install
- 若加群了没同意(一般不会超过一天),那就是群满了,请换一个群试试
- 查看开发文档、<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">vue-next-admin</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>
# 运行项目
cnpm run serve
<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>
# 打包发布
cnpm run build
```
#### 学习交流加 QQ 群
- 加群下载基础模板、查看开发文档、<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">vue-next-admin</a> 开发文档正在编写中...
- 群号码:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
<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/qqs.png" width="220" alt="vue-next-admin 讨论群" title="vue-next-admin 讨论群"/></a>
#### 鸣谢列表
#### ❤️ 鸣谢列表
- <a href="https://github.com/vuejs/vue" target="_blank">vue</a>
- <a href="https://github.com/vuejs/vue-next" target="_blank">vue-next</a>
@ -102,7 +93,6 @@ cnpm run build
- <a href="https://github.com/vuejs/vue-router-next" target="_blank">vue-router-nex</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/PanJiaChen/vue-element-admin" target="_blank">vue-element-admin</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>
@ -115,12 +105,13 @@ cnpm run build
- <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/antvis/g6" target="_blank">@antv/g6</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>
#### 特别感谢
#### 💕 特别感谢
特别感谢群里老哥的建议、指导与帮忙,谢谢!
@ -128,8 +119,7 @@ cnpm run build
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参
- <a href="https://gitee.com/chuange" target="_blank">@川歌</a>
#### 其他事项
#### 💌 支持作者
- <a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">vue3.x vue-next-admin 版本</a>,基于 vue3.x + CompositionAPI + typescript + vite + element plus。
- <a href="http://lyt-top.gitee.io/vue-admin-wonderful-preview/#/login" target="_blank">vue2.x vue-prev-admin 版本</a>,基于 vue2.x + element ui
- 喜欢用就帮忙 <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">gitee star</a> 下,不喜欢用也没关系,出来打工、创业,大家都不容易,感谢大家的支持,谢谢!
如果觉得框架不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/lyt-Top/vue-next-admin">Github</a> 或者
<a target="_blank" href="https://gitee.com/lyt-top/vue-next-admin">Gitee</a> 帮我点个 ⭐ Star这将是对我极大的鼓励与支持

7904
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,58 @@
{
"name": "vue-next-admin",
"version": "1.0.0",
"version": "1.0.16",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@antv/g6": "^4.2.5",
"axios": "^0.21.1",
"clipboard": "^2.0.8",
"countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.0.2",
"countup.js": "^2.0.8",
"cropperjs": "^1.5.12",
"echarts": "^5.1.2",
"echarts-gl": "^2.0.8",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^1.0.2-beta.39",
"mitt": "^2.1.0",
"element-plus": "^1.0.2-beta.71",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^5.1.0",
"sortablejs": "^1.13.0",
"vue": "^3.0.5",
"vue-i18n": "^9.1.3",
"vue-router": "^4.0.2",
"vue-web-screen-shot": "^1.1.8",
"vuex": "^4.0.0-rc.2",
"wangeditor": "^4.6.14"
"sortablejs": "^1.14.0",
"splitpanes": "^3.0.4",
"vue": "^3.1.5",
"vue-clipboard3": "^1.0.1",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.7",
"vue-router": "^4.0.10",
"vue-web-screen-shot": "^1.2.0",
"vuex": "^4.0.2",
"wangeditor": "^4.7.7"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^14.14.37",
"@types/node": "^16.7.1",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.10.6",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"@vitejs/plugin-vue": "^1.2.1",
"@vue/compiler-sfc": "^3.0.11",
"dotenv": "^8.2.0",
"eslint": "^7.23.0",
"eslint-plugin-vue": "^7.8.0",
"prettier": "^2.2.1",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"typescript": "^4.2.4",
"vite": "^2.1.5",
"vue-eslint-parser": "^7.6.0"
}
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.2.4",
"dotenv": "^10.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.16.0",
"prettier": "^2.3.2",
"sass": "^1.38.0",
"sass-loader": "^12.1.0",
"typescript": "^4.3.5",
"vite": "^2.5.0",
"vue-eslint-parser": "^7.10.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

1
plugins.d.ts vendored Normal file
View File

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

9
shim.d.ts vendored
View File

@ -1,4 +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;
}

View File

@ -1,27 +1,34 @@
<template>
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
<LockScreen v-if="getThemeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
<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 lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStore } from '/@/store/index.ts';
import { getLocal } from '/@/utils/storage.ts';
import setIntroduction from '/@/utils/setIconfont.ts';
import LockScreen from '/@/views/layout/lockScreen/index.vue';
import Setings from '/@/views/layout/navBars/breadcrumb/setings.vue';
import { useStore } from '/@/store/index';
import { useTitle } from '/@/utils/setWebTitle';
import { Local } 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 },
components: { LockScreen, Setings, CloseFull },
setup() {
const { t } = useI18n();
const { proxy } = getCurrentInstance() as any;
const setingsRef = ref();
const route = useRoute();
const store = useStore();
const title = useTitle();
const state = reactive({
i18nLocale: null,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
@ -44,31 +51,33 @@ export default defineComponent({
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 设置 i18nApp.vue 中的 el-config-provider
proxy.mittBus.on('getI18nConfig', (locale: string) => {
state.i18nLocale = locale;
});
// 获取缓存中的布局配置
if (getLocal('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', getLocal('themeConfig'));
document.documentElement.style.cssText = getLocal('themeConfigStyle');
if (Local.get('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
});
});
// 页面销毁时,关闭监听布局配置
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
proxy.mittBus.off('openSetingsDrawer', () => {});
proxy.mittBus.off('getI18nConfig', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
nextTick(() => {
let webTitle = '';
route.path === '/login' ? (webTitle = route.meta.title) : (webTitle = t(route.meta.title));
document.title = `${webTitle} - ${getThemeConfig.value.globalTitle}` || getThemeConfig.value.globalTitle;
});
title();
}
);
return {
setingsRef,
getThemeConfig,
...toRefs(state),
};
},
});

View File

@ -1,6 +1,10 @@
import request from '/@/utils/request.ts';
import request from '/@/utils/request';
// 用户登录
/**
* 用户登录
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signIn(params: object) {
return request({
url: '/user/signIn',
@ -9,7 +13,11 @@ export function signIn(params: object) {
});
}
// 用户退出登录
/**
* 用户退出登录
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signOut(params: object) {
return request({
url: '/user/signOut',

View File

@ -1,10 +1,16 @@
import request from '/@/utils/request.ts';
import request from '/@/utils/request';
/**
* 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由isRequestRoutes 为 true则开启后端控制路由
*/
// 获取后端动态路由菜单(admin)
/**
* 获取后端动态路由菜单(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',
@ -13,7 +19,12 @@ export function getMenuAdmin(params?: object) {
});
}
// 获取后端动态路由菜单(test)
/**
* 获取后端动态路由菜单(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',

View File

@ -6,7 +6,7 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default {
name: 'auth',
props: {

View File

@ -6,8 +6,8 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import { judementSameArr } from '/@/utils/arrayOperation.ts';
import { useStore } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';
export default {
name: 'authAll',
props: {

View File

@ -6,7 +6,7 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default {
name: 'auths',
props: {

View File

@ -1,57 +1,49 @@
<template>
<div class="icon-selector">
<el-popover :placement="placement" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
<template #reference>
<el-input
v-model="fontIcon"
placeholder="请点击选择图标"
clearable
size="small"
v-model="fontIconSearch"
:placeholder="fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
:prefix-icon="fontIconPrefix"
@clear="onClearFontIcon"
></el-input>
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<i
:class="[
fontIconPrefix === '' ? prepend : fontIconPrefix,
{ iconfont: fontIconTabsIndex === 0 },
{ ele: fontIconTabsIndex === 1 },
{ fa: fontIconTabsIndex === 2 },
]"
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">请选择一个图标</div>
<div v-if="isAllOn" class="icon-selector-all">
<el-input v-model="fontIconSearch" placeholder="请输入内容进行搜索" size="small"></el-input>
<div class="icon-selector-all-tabs">
<div
class="icon-selector-all-tabs-item"
v-for="(v, k) in fontIconTabsList"
:key="k"
@click="onFontIconTabsClick(v, k)"
:class="{ 'icon-selector-all-tabs-active': fontIconTabsIndex === k }"
>
<div class="label">{{ v.label }}</div>
</div>
</div>
</div>
<div class="icon-selector-warp-title">{{ title }}</div>
<div class="icon-selector-warp-row">
<el-row :gutter="10">
<el-col
:xs="4"
:sm="4"
:md="2"
:lg="2"
:xl="1"
:class="`${fontIconTabsIcon}-col`"
@click="onColClick(v, k)"
v-for="(v, k) in fontIconSheetsFilterList"
:key="k"
>
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconIndex === k }">
<div class="flex-margin">
<div class="icon-selector-warp-item-value">
<i :class="[fontIconTabsIcon, v]"></i>
<el-scrollbar>
<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">
<i :class="v"></i>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0"></el-empty>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
</el-scrollbar>
</div>
</div>
</transition>
@ -60,40 +52,87 @@
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed } from 'vue';
import initIconfont from '/@/utils/getStyleSheets.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: {
// 是否开启高级功能
isAllOn: {
// 输入框前置内容
prepend: {
type: String,
default: () => 'el-icon-thumb',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'small',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 出现位置
placement: {
type: String,
default: () => 'bottom',
// 是否可清空
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 state: any = reactive({
fontIcon: '',
fontIconPrefix: '',
fontIconVisible: false,
fontIconWidth: 0,
fontIconIndex: '',
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconTabsIcon: 'iconfont ali',
fontIconTabsList: [{ label: 'iconfont' }, { label: 'element' }, { label: 'awesome' }],
fontIconSheetsList: [],
fontIconSheetsListAli: [],
fontIconSheetsListEle: [],
fontIconSheetsListAwe: [],
fontIconPlaceholder: '',
});
// 设置无数据时的空状态
// 处理 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();
@ -114,43 +153,46 @@ export default {
});
};
// 初始化数据
const initFontIconData = () => {
initIconfont.ali().then((res: any) => {
state.fontIconSheetsList = res;
state.fontIconSheetsListAli = res;
});
initIconfont.ele().then((res: any) => {
state.fontIconSheetsListEle = res;
});
initIconfont.awe().then((res: any) => {
state.fontIconSheetsListAwe = res;
});
const initFontIconData = async () => {
if (props.type === 'ali') {
await initIconfont.ali().then((res: any) => {
state.fontIconTabsIndex = 0;
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
});
} else if (props.type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconTabsIndex = 1;
state.fontIconSheetsList = res;
});
} else if (props.type === 'awe') {
await initIconfont.awe().then((res: any) => {
state.fontIconTabsIndex = 2;
// 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();
};
// 当前点击
const onColClick = (v: any, k: number) => {
state.fontIconIndex = k;
state.fontIcon = v;
// 获取当前点击的 icon 图标
const onColClick = (v: any) => {
state.fontIconPlaceholder = v;
state.fontIconVisible = false;
if (state.fontIconTabsIndex === 0) state.fontIconPrefix = `iconfont ali ${v}`;
else if (state.fontIconTabsIndex === 1) state.fontIconPrefix = `ele ${v}`;
else if (state.fontIconTabsIndex === 2) state.fontIconPrefix = `fa awe ${v}`;
if (state.fontIconTabsIndex === 0) state.fontIconPrefix = `${v}`;
else if (state.fontIconTabsIndex === 1) state.fontIconPrefix = `${v}`;
else if (state.fontIconTabsIndex === 2) state.fontIconPrefix = `${v}`;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// input 点击清除按钮时
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconIndex = '';
state.fontIconPrefix = '';
emit('get', state.fontIconPrefix);
};
// tabs 点击
const onFontIconTabsClick = (v: any, k: number) => {
state.fontIconTabsIndex = k;
if (v.label === 'iconfont') state.fontIconSheetsList = state.fontIconSheetsListAli;
else if (v.label === 'element') state.fontIconSheetsList = state.fontIconSheetsListEle;
else if (v.label === 'awesome') state.fontIconSheetsList = state.fontIconSheetsListAwe;
if (k === 0) state.fontIconTabsIcon = `iconfont ali`;
else if (k === 1) state.fontIconTabsIcon = `ele`;
else if (k === 2) state.fontIconTabsIcon = `fa awe`;
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 页面加载时
onMounted(() => {
@ -158,12 +200,20 @@ export default {
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
fontIconSheetsFilterList,
onColClick,
onClearFontIcon,
onFontIconTabsClick,
onIconFocus,
onIconBlur,
...toRefs(state),
};
},

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>
<i v-if="rightIcon" class="notice-bar-warp-right-icon" :class="rightIcon" @click="onRightIconClick"></i>
</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

@ -2,18 +2,21 @@ 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.ts';
import { store } from '/@/store/index';
import nextZhcn from '/@/i18n/lang/zh-cn.ts';
import nextEn from '/@/i18n/lang/en.ts';
import nextZhtw from '/@/i18n/lang/zh-tw.ts';
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.ts';
import pagesHomeEn from '/@/i18n/pages/home/en.ts';
import pagesHomeZhtw from '/@/i18n/pages/home/zh-tw.ts';
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn.ts';
import pagesLoginEn from '/@/i18n/pages/login/en.ts';
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw.ts';
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';
// 定义语言国际化内容
/**
@ -23,27 +26,30 @@ import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw.ts';
*/
const messages = {
[zhcnLocale.name]: {
el: zhcnLocale.el,
...zhcnLocale,
message: {
...nextZhcn,
...pagesHomeZhcn,
...pagesLoginZhcn,
...pagesFormI18nZhcn,
},
},
[enLocale.name]: {
el: enLocale.el,
...enLocale,
message: {
...nextEn,
...pagesHomeEn,
...pagesLoginEn,
...pagesFormI18nEn,
},
},
[zhtwLocale.name]: {
el: zhtwLocale.el,
...zhtwLocale,
message: {
...nextZhtw,
...pagesHomeZhtw,
...pagesLoginZhtw,
...pagesFormI18nZhtw,
},
},
};

View File

@ -24,14 +24,16 @@ export default {
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',
pagesIndex: 'pages',
pagesFiltering: 'Filtering',
pagesFilteringDetails: 'FilteringDetails',
@ -41,27 +43,53 @@ export default {
pagesAwesome: 'awesome icon',
pagesCityLinkage: 'CityLinkage',
pagesFormAdapt: 'FormAdapt',
pagesFormI18n: 'FormI18n',
pagesFormRules: 'Multi form validation',
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',
layoutIfameView: 'IfameView',
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
user: {
title0: 'Component size',
title1: 'Language switching',
title2: 'Menu search',
title3: 'Layout configuration',
title4: 'news',
title5: 'Full screen on',
title6: 'Full screen off',
dropdownDefault: 'default',
dropdownMedium: 'medium',
dropdownSmall: 'small',
dropdownMini: 'mini',
dropdown1: 'home page',
dropdown2: 'Personal Center',
dropdown3: '404',
dropdown4: '401',
dropdown5: 'Log out',
dropdown6: 'Code warehouse',
searchPlaceholder: 'Menu search: support Chinese, routing path',
newTitle: 'notice',
newBtn: 'All read',
@ -80,6 +108,7 @@ export default {
closeOther: 'closeOther',
closeAll: 'closeAll',
fullscreen: 'fullscreen',
closeFullscreen: 'closeFullscreen',
},
notFound: {
foundTitle: 'Wrong address input, please re-enter the address~',
@ -103,6 +132,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',
@ -119,6 +149,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',
@ -128,6 +159,7 @@ export default {
fiveTagsStyle: 'Tagsview style',
fiveAnimation: 'page animation',
fiveColumnsAsideStyle: 'Column style',
fiveColumnsAsideLayout: 'Column layout',
sixTitle: 'Layout switch',
sixDefaults: 'One',
sixClassic: 'Two',

View File

@ -24,14 +24,16 @@ export default {
funCountup: 'countup 数字滚动',
funEchartsTree: 'echartsTree 树图',
funSelector: '图标选择器',
funNoticeBar: '滚动通知栏',
funWangEditor: 'wangEditor 编辑器',
funCropper: 'cropper 图片裁剪',
funMindMap: 'G6 思维导图',
funQrcode: 'qrcode 二维码生成',
funEchartsMap: '地理坐标/地图',
funPrintJs: '页面打印',
funClipboard: '复制剪切',
funScreenShort: 'web端自定义截屏',
funGridLayout: '拖拽布局',
funSplitpanes: '窗格拆分器',
pagesIndex: '页面',
pagesFiltering: '过滤筛选组件',
pagesFilteringDetails: '过滤筛选组件详情',
@ -41,27 +43,53 @@ export default {
pagesAwesome: 'awesome 字体图标',
pagesCityLinkage: '城市多级联动',
pagesFormAdapt: '表单自适应',
pagesFormI18n: '表单国际化',
pagesFormRules: '多表单验证',
pagesListAdapt: '列表自适应',
pagesWaterfall: '瀑布屏',
pagesSteps: '步骤条',
pagesPreview: '大图预览',
pagesWaves: '波浪效果',
pagesTree: '树形改表格',
pagesDrag: '拖动指令',
pagesLazyImg: '图片懒加载',
paramsIndex: '路由参数',
paramsCommon: '普通路由',
paramsDynamic: '动态路由',
paramsCommonDetails: '普通路由详情',
paramsDynamicDetails: '动态路由详情',
chartIndex: '大数据图表',
visualizingIndex: '数据可视化',
visualizingLinkDemo1: '数据可视化演示1',
visualizingLinkDemo2: '数据可视化演示2',
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
user: {
title0: '组件大小',
title1: '语言切换',
title2: '菜单搜索',
title3: '布局配置',
title4: '消息',
title5: '开全屏',
title6: '关全屏',
dropdownDefault: '默认',
dropdownMedium: '中等',
dropdownSmall: '小型',
dropdownMini: '超小',
dropdown1: '首页',
dropdown2: '个人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '退出登录',
dropdown6: '代码仓库',
searchPlaceholder: '菜单搜索:支持中文、路由路径',
newTitle: '通知',
newBtn: '全部已读',
@ -80,6 +108,7 @@ export default {
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
},
notFound: {
foundTitle: '地址输入错误,请重新输入地址~',
@ -103,6 +132,7 @@ export default {
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsMenuBarColorHighlight: '菜单字体背景高亮',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
@ -119,6 +149,7 @@ export default {
fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsShareTagsView: '开启 TagsView 共用',
fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
@ -128,6 +159,7 @@ export default {
fiveTagsStyle: 'Tagsview 风格',
fiveAnimation: '主页面切换动画',
fiveColumnsAsideStyle: '分栏高亮风格',
fiveColumnsAsideLayout: '分栏布局风格',
sixTitle: '布局切换',
sixDefaults: '默认',
sixClassic: '经典',

View File

@ -24,14 +24,16 @@ export default {
funCountup: 'countup 數位滾動',
funEchartsTree: 'echartsTree 樹圖',
funSelector: '圖標選擇器',
funNoticeBar: '滾動通知欄',
funWangEditor: 'wangEditor 編輯器',
funCropper: 'cropper 圖片裁剪',
funMindMap: 'G6 心智圖',
funQrcode: 'qrcode 二維碼生成',
funEchartsMap: '地理座標/地圖',
funPrintJs: '頁面列印',
funClipboard: '複製剪切',
funScreenShort: '自定義截圖',
funGridLayout: '拖拽佈局',
funSplitpanes: '窗格折開器',
pagesIndex: '頁面',
pagesFiltering: '過濾篩選組件',
pagesFilteringDetails: '過濾篩選組件詳情',
@ -41,27 +43,53 @@ export default {
pagesAwesome: 'awesome 字體圖標',
pagesCityLinkage: '都市多級聯動',
pagesFormAdapt: '表單自我調整',
pagesFormI18n: '表單國際化',
pagesFormRules: '多表單驗證',
pagesListAdapt: '清單自我調整',
pagesWaterfall: '瀑布屏',
pagesSteps: '步驟條',
pagesPreview: '大圖預覽',
pagesWaves: '波浪效果',
pagesTree: '樹形改表格',
pagesDrag: '拖動指令',
pagesLazyImg: '圖片懶加載',
paramsIndex: '路由參數',
paramsCommon: '普通路由',
paramsDynamic: '動態路由',
paramsCommonDetails: '普通路由詳情',
paramsDynamicDetails: '動態路由詳情',
chartIndex: '大資料圖表',
visualizingIndex: '數據視覺化',
visualizingLinkDemo1: '數據視覺化演示1',
visualizingLinkDemo2: '數據視覺化演示2',
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登入',
notFound: '找不到此頁面',
noPower: '沒有許可權',
},
user: {
title0: '組件大小',
title1: '語言切換',
title2: '選單蒐索',
title3: '佈局配寘',
title4: '消息',
title5: '開全屏',
title6: '關全屏',
dropdownDefault: '默認',
dropdownMedium: '中等',
dropdownSmall: '小型',
dropdownMini: '超小',
dropdown1: '首頁',
dropdown2: '個人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '登出',
dropdown6: '程式碼倉庫',
searchPlaceholder: '選單蒐索:支援中文、路由路徑',
newTitle: '通知',
newBtn: '全部已讀',
@ -80,6 +108,7 @@ export default {
closeOther: '關閉其它',
closeAll: '全部關閉',
fullscreen: '當前頁全屏',
closeFullscreen: '關閉全屏',
},
notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~',
@ -103,6 +132,7 @@ export default {
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsTopBarColorGradual: '頂欄背景漸變',
twoIsMenuBarColorGradual: '選單背景漸變',
twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
twoIsMenuBarColorHighlight: '選單字體背景高亮',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
@ -119,6 +149,7 @@ export default {
fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsShareTagsView: '開啟 TagsView 共用',
fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
@ -128,6 +159,7 @@ export default {
fiveTagsStyle: 'Tagsview 風格',
fiveAnimation: '主頁面切換動畫',
fiveColumnsAsideStyle: '分欄高亮風格',
fiveColumnsAsideLayout: '分欄佈局風格',
sixTitle: '佈局切換',
sixDefaults: '默認',
sixClassic: '經典',

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,25 +1,19 @@
<template>
<el-aside class="layout-aside" :class="setCollapseWidth" v-if="clientWidth > 1000">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
<Vertical :menuList="menuList" :class="setCollapseWidth" />
</el-scrollbar>
</el-aside>
<el-drawer v-model="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="layoutAsideScrollbarRef">
<Vertical :menuList="menuList" />
</el-scrollbar>
</el-aside>
</el-drawer>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted } from 'vue';
import { useStore } from '/@/store/index.ts';
import Logo from '/@/views/layout/logo/index.vue';
import Vertical from '/@/views/layout/navMenu/vertical.vue';
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: { Logo, Vertical },
@ -34,26 +28,55 @@ export default {
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
//
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// /
const setCollapseWidth = computed(() => {
let { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
let asideBrColor = menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
if (layout === 'columns') {
// 1px
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'];
}
}
}
});
//
const closeLayoutAsideMobileMode = () => {
const el = document.querySelector('.layout-aside-mobile-mode');
el && el.parentNode?.removeChild(el);
store.state.themeConfig.themeConfig.isCollapse = false;
document.body.setAttribute('class', '');
};
// / logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
@ -85,9 +108,8 @@ export default {
proxy.$refs.layoutAsideScrollbarRef.update();
}
});
//
// vuex
watch(store.state, (val) => {
if (val.routesList.routesList.length === state.menuList.length) return false;
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
@ -96,6 +118,8 @@ export default {
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// (proxy.mittBus.off('setSendColumnsChildren))
// 使
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
@ -111,19 +135,14 @@ export default {
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
//
onUnmounted(() => {
proxy.mittBus.off('setSendColumnsChildren');
proxy.mittBus.off('setSendClassicChildren');
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
proxy.mittBus.off('layoutMobileResize');
});
return {
setCollapseWidth,
setCollapseStyle,
setShowLogo,
getThemeConfig,
isTagsViewCurrenFull,
...toRefs(state),
};
},

View File

@ -14,17 +14,25 @@
:class="{ 'layout-columns-active': liIndex === k }"
:title="$t(v.meta.title)"
>
<div class="layout-columns-aside-li-box" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<i :class="v.meta.icon"></i>
<div class="layout-columns-aside-li-box-title font12">
{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, 4) : $t(v.meta.title) }}
<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)
: $t(v.meta.title)
}}
</div>
</div>
<div class="layout-columns-aside-li-box" v-else>
<div :class="setColumnsAsidelayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<i :class="v.meta.icon"></i>
<div class="layout-columns-aside-li-box-title font12">
{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, 4) : $t(v.meta.title) }}
<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)
: $t(v.meta.title)
}}
</div>
</a>
</div>
@ -38,7 +46,7 @@
<script lang="ts">
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default {
name: 'layoutColumnsAside',
setup() {
@ -54,10 +62,14 @@ export default {
difference: 0,
routeSplit: [],
});
//
//
const setColumnsAsideStyle = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideStyle;
});
//
const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout;
});
//
const setColumnsAsideMove = (k: number) => {
state.liIndex = k;
@ -80,6 +92,7 @@ export default {
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);
};
@ -113,16 +126,15 @@ export default {
state.routeSplit.shift();
const routeFirst = `/${state.routeSplit[0]}`;
const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
if (!currentSplitRoute) return false;
//
setTimeout(() => {
onColumnsAsideDown(currentSplitRoute.k);
}, 0);
};
//
//
watch(store.state, (val) => {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (val.routesList.routesList.length === state.columnsAsideList.length) return false;
setFilterRoutes();
});
//
onMounted(() => {
@ -138,6 +150,7 @@ export default {
columnsAsideActiveRef,
onColumnsAsideDown,
setColumnsAsideStyle,
setColumnsAsidelayout,
onColumnsAsideMenuClick,
...toRefs(state),
};
@ -147,7 +160,7 @@ export default {
<style scoped lang="scss">
.layout-columns-aside {
width: 64px;
width: 70px;
height: 100%;
background: var(--bg-columnsMenuBar);
ul {
@ -161,12 +174,28 @@ export default {
cursor: pointer;
position: relative;
z-index: 1;
.layout-columns-aside-li-box {
.columns-vertical {
margin: auto;
.layout-columns-aside-li-box-title {
.columns-vertical-title {
padding-top: 1px;
}
}
.columns-horizontal {
display: flex;
height: 50px;
width: 100%;
align-items: center;
padding: 0 5px;
i {
margin-right: 3px;
}
a {
display: flex;
.columns-horizontal-title {
padding-top: 1px;
}
}
}
a {
text-decoration: none;
color: var(--bg-columnsMenuBarColor);
@ -183,7 +212,7 @@ export default {
left: 50%;
top: 2px;
height: 44px;
width: 58px;
width: 65px;
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;

View File

@ -1,13 +1,13 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight">
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import NavBarsIndex from '/@/views/layout/navBars/index.vue';
import { useStore } from '/@/store/index';
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default {
name: 'layoutHeader',
components: { NavBarsIndex },
@ -19,8 +19,13 @@ export default {
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
//
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},
};

View File

@ -3,68 +3,53 @@
<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 />
<Footer v-if="getThemeConfig.isFooter" />
</el-scrollbar>
<Link :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 lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
import { useStore } from '/@/store/index';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import LayoutParentView from '/@/views/layout/routerView/parent.vue';
import Footer from '/@/views/layout/footer/index.vue';
import Link from '/@/views/layout/routerView/link.vue';
import Iframes from '/@/views/layout/routerView/iframes.vue';
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footer from '/@/layout/footer/index.vue';
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footer, Link, Iframes },
components: { LayoutParentView, Footer },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const store = useStore();
const state = reactive({
headerHeight: '',
currentRouteMeta: {},
isShowLink: false,
});
//
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
//
const onGetCurrentRouteMeta = () => {
initCurrentRouteMeta(route.meta);
};
// meta
const initCurrentRouteMeta = (meta: object) => {
state.isShowLink = false;
state.currentRouteMeta = meta;
setTimeout(() => {
state.isShowLink = true;
}, 100);
};
// 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(() => {
initCurrentRouteMeta(route.meta);
initHeaderHeight();
initGetMeta();
});
// themeConfig el-scrollbar
watch(store.state.themeConfig.themeConfig, (val) => {
@ -74,18 +59,15 @@ export default defineComponent({
proxy.$refs.layoutScrollbarRef.update();
}
});
//
//
watch(
() => route.path,
() => {
initCurrentRouteMeta(route.meta);
proxy.$refs.layoutScrollbarRef.wrap.scrollTop = 0;
state.currentRouteMeta = route.meta;
}
);
return {
getThemeConfig,
initCurrentRouteMeta,
onGetCurrentRouteMeta,
...toRefs(state),
};
},

View File

@ -2,7 +2,7 @@
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">Copyright 深圳市 xxx 软件科技有限公司</div>
<div class="mt5">{{ $t('message.copyright.one5') }}</div>
</div>
</div>
</template>

View File

@ -6,17 +6,16 @@
</template>
<script lang="ts">
import { computed, onBeforeMount, onUnmounted, getCurrentInstance, defineAsyncComponent } from 'vue';
import { useStore } from '/@/store/index.ts';
import { getLocal } from '/@/utils/storage.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: defineAsyncComponent(() => import('/@/views/layout/main/defaults.vue')),
Classic: defineAsyncComponent(() => import('/@/views/layout/main/classic.vue')),
Transverse: defineAsyncComponent(() => import('/@/views/layout/main/transverse.vue')),
Columns: defineAsyncComponent(() => import('/@/views/layout/main/columns.vue')),
},
components: { Defaults, Classic, Transverse, Columns },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
@ -26,6 +25,7 @@ export default {
});
// ()
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
getThemeConfig.value.isCollapse = false;
@ -35,7 +35,7 @@ export default {
});
} else {
proxy.mittBus.emit('layoutMobileResize', {
layout: getLocal('oldLayout') ? getLocal('oldLayout') : 'defaults',
layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
clientWidth,
});
}

View File

@ -19,6 +19,10 @@
</div>
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
</div>
<div class="layout-lock-screen-date-top">
<i class="el-icon-top"></i>
<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">
@ -52,11 +56,11 @@
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index.ts';
import { formatDate } from '/@/utils/formatTime.ts';
import { setLocal } from '/@/utils/storage.ts';
export default {
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;
@ -154,7 +158,7 @@ export default {
//
const setLocalThemeConfig = () => {
store.state.themeConfig.themeConfig.isDrawer = false;
setLocal('themeConfig', store.state.themeConfig.themeConfig);
Local.set('themeConfig', store.state.themeConfig.themeConfig);
};
//
const onLockScreenSubmit = () => {
@ -182,7 +186,7 @@ export default {
...toRefs(state),
};
},
};
});
</script>
<style scoped lang="scss">
@ -195,6 +199,8 @@ export default {
}
.layout-lock-screen-filter {
filter: blur(5px);
transform: scale(1.01);
transition: all 0.1s 0.1s ease-in-out;
}
.layout-lock-screen-mask {
background: rgba(255, 255, 255, 1);
@ -203,7 +209,7 @@ export default {
}
.layout-lock-screen-img {
@extend .layout-lock-screen-fixed;
background-image: url('https://img6.bdstatic.com/img/image/pcindex/sunjunpchuazhoutu.JPG');
background-image: url('https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/03.jpg');
background-size: 100% 100%;
z-index: 9999991;
}
@ -233,6 +239,54 @@ export default {
font-size: 16px;
}
}
&-top {
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 100%;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
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: #ffffff;
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: #ffffff;
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;

View File

@ -10,7 +10,7 @@
<script lang="ts">
import { computed, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default {
name: 'layoutLogo',
setup() {

View File

@ -14,11 +14,11 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import Aside from '/@/views/layout/component/aside.vue';
import Header from '/@/views/layout/component/header.vue';
import Main from '/@/views/layout/component/main.vue';
import TagsView from '/@/views/layout/navBars/tagsView/tagsView.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: { Aside, Header, Main, TagsView },

View File

@ -3,9 +3,9 @@
<ColumnsAside />
<div class="layout-columns-warp">
<Aside />
<el-container class="flex-center layout-backtop">
<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar>
<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</el-scrollbar>
@ -17,11 +17,11 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import Aside from '/@/views/layout/component/aside.vue';
import Header from '/@/views/layout/component/header.vue';
import Main from '/@/views/layout/component/main.vue';
import ColumnsAside from '/@/views/layout/component/columnsAside.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: { Aside, Header, Main, ColumnsAside },

View File

@ -1,9 +1,9 @@
<template>
<el-container class="layout-container">
<Aside />
<el-container class="flex-center layout-backtop">
<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef">
<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</el-scrollbar>
@ -15,15 +15,15 @@
<script lang="ts">
import { computed, getCurrentInstance, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import Aside from '/@/views/layout/component/aside.vue';
import Header from '/@/views/layout/component/header.vue';
import Main from '/@/views/layout/component/main.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';
export default {
name: 'layoutDefaults',
components: { Aside, Header, Main },
setup() {
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const isFixedHeader = computed(() => {

View File

@ -7,8 +7,8 @@
</template>
<script lang="ts">
import Header from '/@/views/layout/component/header.vue';
import Main from '/@/views/layout/component/main.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
export default {
name: 'layoutTransverse',
components: { Header, Main },

View File

@ -1,5 +1,5 @@
<template>
<div class="layout-navbars-breadcrumb" v-show="getThemeConfig.isBreadcrumb">
<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'"
@ -23,7 +23,7 @@
<script lang="ts">
import { toRefs, reactive, computed, getCurrentInstance, onMounted } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default {
name: 'layoutBreadcrumb',
setup() {
@ -41,6 +41,16 @@ export default {
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;
@ -85,6 +95,7 @@ export default {
});
return {
onThemeConfigChange,
isShowBreadcrumb,
getThemeConfig,
onBreadcrumbClick,
...toRefs(state),

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">
<i class="el-icon-close"></i>
</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

@ -8,13 +8,13 @@
</template>
<script lang="ts">
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, watch } from 'vue';
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import Breadcrumb from '/@/views/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '/@/views/layout/navBars/breadcrumb/user.vue';
import Logo from '/@/views/layout/logo/index.vue';
import Horizontal from '/@/views/layout/navMenu/horizontal.vue';
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 },
@ -81,11 +81,6 @@ export default {
});
return currentData;
};
//
watch(store.state, (val) => {
if (val.routesList.routesList.length === state.menuList.length) return false;
setFilterRoutes();
});
//
onMounted(() => {
setFilterRoutes();
@ -114,7 +109,6 @@ export default {
align-items: center;
padding-right: 15px;
background: var(--bg-topBar);
overflow: hidden;
border-bottom: 1px solid #f1f2f3;
}
</style>

View File

@ -21,11 +21,13 @@
<script lang="ts">
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from '/@/store/index.ts';
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({
@ -56,7 +58,8 @@ export default defineComponent({
return (restaurant: any) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
t(restaurant.meta.title).indexOf(queryString.toLowerCase()) > -1
);
};
};

View File

@ -94,6 +94,12 @@
<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">
@ -158,12 +164,15 @@
<el-switch v-model="getThemeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
<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 === 'transverse'"
:disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
@change="onIsBreadcrumbChange"
></el-switch>
</div>
@ -198,6 +207,12 @@
<el-switch v-model="getThemeConfig.isSortableTagsView" @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">
@ -252,7 +267,7 @@
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
<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">
@ -261,6 +276,15 @@
</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>
@ -338,7 +362,7 @@
icon="el-icon-document-copy"
type="primary"
ref="copyConfigBtnRef"
@click="onCopyConfigClick($event.target)"
@click="onCopyConfigClick"
>{{ $t('message.layout.copyText') }}
</el-button>
</div>
@ -348,22 +372,19 @@
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, ref } from 'vue';
import { ElMessage } from 'element-plus';
import ClipboardJS from 'clipboard';
import { useI18n } from 'vue-i18n';
import { useStore } from '/@/store/index.ts';
import { getLightColor } from '/@/utils/theme.ts';
import Watermark from '/@/utils/wartermark.ts';
import { verifyAndSpace } from '/@/utils/toolsValidate.ts';
import { setLocal, getLocal, removeLocal } from '/@/utils/storage.ts';
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed } 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';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { t } = useI18n();
const { proxy } = getCurrentInstance() as any;
const copyConfigBtnRef = ref();
const store = useStore();
const { copyText } = commonFunction();
//
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
@ -385,6 +406,7 @@ export default defineComponent({
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2 / -->
@ -395,6 +417,10 @@ export default defineComponent({
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(() => {
@ -403,10 +429,6 @@ export default defineComponent({
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();
const elNavbars: any = document.querySelector('.layout-navbars-breadcrumb-index');
const elAside: any = document.querySelector('.layout-container .el-aside');
if (elNavbars) setLocal('navbarsBgStyle', elNavbars.style.cssText);
if (elAside) setLocal('asideBgStyle', elAside.style.cssText);
});
};
// 2 / -->
@ -419,7 +441,7 @@ export default defineComponent({
if (getThemeConfig.value.isMenuBarColorHighlight) {
elsItems.forEach((el: any) => el.setAttribute('id', ``));
elActive.setAttribute('id', `add-is-active`);
setLocal('menuBarHighlightId', elActive.getAttribute('id'));
Local.set('menuBarHighlightId', elActive.getAttribute('id'));
} else {
elActive.setAttribute('id', ``);
}
@ -460,6 +482,11 @@ export default defineComponent({
proxy.mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4 --> TagsView
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4 --> /
const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') {
@ -469,10 +496,9 @@ export default defineComponent({
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle: any = document.querySelector('#app');
const appEle: any = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
setLocal('appFilterStyle', appEle.style.cssText);
};
// 4 -->
const onWartermarkChange = () => {
@ -488,59 +514,13 @@ export default defineComponent({
};
// 5
const onSetLayout = (layout: string) => {
setLocal('oldLayout', layout);
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
getThemeConfig.value.layout = layout;
getThemeConfig.value.isDrawer = false;
initSetLayoutChange();
initLayoutChangeFun();
onMenuBarHighlightChange();
};
//
const initSetLayoutChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isShowLogo = true;
getThemeConfig.value.isBreadcrumb = false;
getThemeConfig.value.isCollapse = false;
getThemeConfig.value.isClassicSplitMenu = false;
getThemeConfig.value.menuBar = '#FFFFFF';
getThemeConfig.value.menuBarColor = '#606266';
getThemeConfig.value.topBar = '#ffffff';
getThemeConfig.value.topBarColor = '#606266';
initLayoutChangeFun();
} else if (getThemeConfig.value.layout === 'transverse') {
getThemeConfig.value.isShowLogo = true;
getThemeConfig.value.isBreadcrumb = false;
getThemeConfig.value.isCollapse = false;
getThemeConfig.value.isTagsview = false;
getThemeConfig.value.isClassicSplitMenu = false;
getThemeConfig.value.menuBarColor = '#FFFFFF';
getThemeConfig.value.topBar = '#545c64';
getThemeConfig.value.topBarColor = '#FFFFFF';
initLayoutChangeFun();
} else if (getThemeConfig.value.layout === 'columns') {
getThemeConfig.value.isShowLogo = true;
getThemeConfig.value.isBreadcrumb = true;
getThemeConfig.value.isCollapse = false;
getThemeConfig.value.isTagsview = true;
getThemeConfig.value.isClassicSplitMenu = false;
getThemeConfig.value.menuBar = '#FFFFFF';
getThemeConfig.value.menuBarColor = '#606266';
getThemeConfig.value.topBar = '#ffffff';
getThemeConfig.value.topBarColor = '#606266';
initLayoutChangeFun();
} else {
getThemeConfig.value.isShowLogo = false;
getThemeConfig.value.isBreadcrumb = true;
getThemeConfig.value.isCollapse = false;
getThemeConfig.value.isTagsview = true;
getThemeConfig.value.isClassicSplitMenu = false;
getThemeConfig.value.menuBar = '#545c64';
getThemeConfig.value.menuBarColor = '#eaeaea';
getThemeConfig.value.topBar = '#FFFFFF';
getThemeConfig.value.topBarColor = '#606266';
initLayoutChangeFun();
}
};
//
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
@ -558,10 +538,6 @@ export default defineComponent({
//
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
nextTick(() => {
//
onCopyConfigClick(copyConfigBtnRef.value.$el);
});
};
// store
const setDispatchThemeConfig = () => {
@ -570,80 +546,72 @@ export default defineComponent({
};
//
const setLocalThemeConfig = () => {
removeLocal('themeConfig');
setLocal('themeConfig', getThemeConfig.value);
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// html
const setLocalThemeConfigStyle = () => {
setLocal('themeConfigStyle', document.documentElement.style.cssText);
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
//
const onCopyConfigClick = (target: any) => {
let copyThemeConfig = getLocal('themeConfig');
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
const clipboard = new ClipboardJS(target, {
text: () => JSON.stringify(copyThemeConfig),
});
clipboard.on('success', () => {
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
ElMessage.success(t('message.layout.copyTextSuccess'));
clipboard.destroy();
});
clipboard.on('error', () => {
ElMessage.error(t('message.layout.copyTextError'));
clipboard.destroy();
});
};
// 退()
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) => {
if (getThemeConfig.value.layout === res.layout) return false;
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initSetLayoutChange();
initLayoutChangeFun();
onMenuBarHighlightChange();
getThemeConfig.value.isCollapse = false;
});
window.addEventListener('load', () => {
//
setTimeout(() => {
//
if (getLocal('navbarsBgStyle') && getThemeConfig.value.isTopBarColorGradual) {
const breadcrumbIndexEl: any = document.querySelector('.layout-navbars-breadcrumb-index');
breadcrumbIndexEl.style.cssText = getLocal('navbarsBgStyle');
}
//
if (getLocal('asideBgStyle') && getThemeConfig.value.isMenuBarColorGradual) {
const asideEl: any = document.querySelector('.layout-container .el-aside');
asideEl.style.cssText = getLocal('asideBgStyle');
}
//
if (getLocal('menuBarHighlightId') && getThemeConfig.value.isMenuBarColorHighlight) {
let els = document.querySelector('.el-menu-item.is-active');
if (!els) return false;
els.setAttribute('id', getLocal('menuBarHighlightId'));
}
// /
if (getLocal('appFilterStyle')) {
const appEl: any = document.querySelector('#app');
appEl.style.cssText = getLocal('appFilterStyle');
}
//
onWartermarkChange();
//
if (getLocal('themeConfig')) proxy.$i18n.locale = getLocal('themeConfig').globalI18n;
}, 1000);
});
setTimeout(() => {
// 退()
initSetStyle();
//
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
//
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
//
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 {
@ -652,6 +620,7 @@ export default defineComponent({
onBgColorPickerChange,
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onMenuBarHighlightChange,
onThemeConfigChange,
onIsFixedHeaderChange,
@ -666,7 +635,7 @@ export default defineComponent({
onClassicSplitMenuChange,
onIsBreadcrumbChange,
onSortableTagsViewChange,
copyConfigBtnRef,
onShareTagsViewChange,
onCopyConfigClick,
};
},

View File

@ -1,5 +1,18 @@
<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>
@ -33,7 +46,7 @@
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="isScreenfull ? $t('message.user.title5') : $t('message.user.title6')"
:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
@ -46,6 +59,7 @@
<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>
@ -63,11 +77,12 @@ 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.ts';
import { useStore } from '/@/store/index.ts';
import { clearSession, setLocal, getLocal, removeLocal } from '/@/utils/storage.ts';
import UserNews from '/@/views/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/views/layout/navBars/breadcrumb/search.vue';
import { resetRoute } from '/@/router/index';
import { useStore } from '/@/store/index';
import { useTitle } from '/@/utils/setWebTitle';
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 },
@ -76,11 +91,13 @@ export default {
const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const store = useStore();
const title = useTitle();
const searchRef = ref();
const state = reactive({
isScreenfull: false,
isShowUserNewsPopover: false,
disabledI18n: false,
disabledI18n: 'zh-cn',
disabledSize: '',
});
// vuex
const getUserInfos = computed(() => {
@ -105,7 +122,10 @@ export default {
return false;
}
screenfull.toggle();
state.isScreenfull = !state.isScreenfull;
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// icon
const onLayoutSetingClick = () => {
@ -138,7 +158,7 @@ export default {
},
})
.then(() => {
clearSession(); // /token
Session.clear(); // /token
resetRoute(); // /
router.push('/login');
setTimeout(() => {
@ -146,6 +166,8 @@ export default {
}, 300);
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
@ -154,31 +176,68 @@ export default {
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) => {
removeLocal('themeConfig');
Local.remove('themeConfig');
getThemeConfig.value.globalI18n = lang;
setLocal('themeConfig', getThemeConfig.value);
Local.set('themeConfig', getThemeConfig.value);
proxy.$i18n.locale = lang;
initI18n();
title();
};
// element plus
const setI18nConfig = (locale: string) => {
proxy.mittBus.emit('getI18nConfig', proxy.$i18n.messages[locale]);
};
//
const initI18n = () => {
switch (getLocal('themeConfig').globalI18n) {
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 (getLocal('themeConfig')) initI18n();
if (Local.get('themeConfig')) {
initI18n();
initComponentSize();
}
});
return {
getUserInfos,
@ -186,6 +245,7 @@ export default {
onHandleCommandClick,
onScreenfullClick,
onSearchClick,
onComponentSizeChange,
onLanguageChange,
searchRef,
layoutUserFlexNum,

View File

@ -7,9 +7,9 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import BreadcrumbIndex from '/@/views/layout/navBars/breadcrumb/index.vue';
import TagsView from '/@/views/layout/navBars/tagsView/tagsView.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 },

View File

@ -11,7 +11,14 @@
>
<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.id)">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
tabindex="-1"
:key="k"
v-if="!v.affix"
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
>
<i :class="v.icon"></i>
<span>{{ $t(v.txt) }}</span>
</li>
@ -35,30 +42,30 @@ export default defineComponent({
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: 'el-icon-refresh-right' },
{ contextMenuClickId: 1, txt: 'message.tagsView.close', affix: false, icon: 'el-icon-close' },
{ contextMenuClickId: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'el-icon-circle-close' },
{ contextMenuClickId: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'el-icon-folder-delete' },
{
id: 4,
contextMenuClickId: 4,
txt: 'message.tagsView.fullscreen',
affix: false,
icon: 'iconfont icon-fullscreen',
},
],
path: {},
item: {},
});
// x,y
const dropdowns = computed(() => {
return props.dropdown;
});
//
const onCurrentContextmenuClick = (id: number) => {
emit('currentContextmenuClick', { id, path: state.path });
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
//
const openContextmenu = (item: any) => {
state.path = item.path;
state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
@ -95,6 +102,7 @@ export default defineComponent({
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;
i {
font-size: 12px !important;
}

View File

@ -7,7 +7,7 @@
:key="k"
class="layout-navbars-tagsview-ul-li"
:data-name="v.name"
:class="{ 'is-active': isActive(v.path) }"
:class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)"
@click="onTagsClick(v, k)"
:ref="
@ -16,21 +16,21 @@
}
"
>
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v.path)"></i>
<i class="layout-navbars-tagsview-ul-li-iconfont" :class="v.meta.icon" v-if="!isActive(v.path) && getThemeConfig.isTagsviewIcon"></i>
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
<i class="layout-navbars-tagsview-ul-li-iconfont" :class="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon"></i>
<span>{{ $t(v.meta.title) }}</span>
<template v-if="isActive(v.path)">
<i class="el-icon-refresh-right ml5" @click.stop="refreshCurrentTagsView(v.path)"></i>
<template v-if="isActive(v)">
<i class="el-icon-refresh-right ml5" @click.stop="refreshCurrentTagsView($route.fullPath)"></i>
<i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-active"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(v.path)"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i>
</template>
<i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-three"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(v.path)"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i>
</li>
</ul>
@ -42,11 +42,12 @@
<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 screenfull from 'screenfull';
import { useStore } from '/@/store/index.ts';
import { setSession, getSession, removeSession } from '/@/utils/storage.ts';
import Sortable from 'sortablejs';
import Contextmenu from '/@/views/layout/navBars/tagsView/contextmenu.vue';
import { ElMessage } from 'element-plus';
import { useStore } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
export default {
name: 'layoutTagsView',
components: { Contextmenu },
@ -60,6 +61,7 @@ export default {
const route = useRoute();
const router = useRouter();
const state: any = reactive({
routeActive: '',
routePath: route.path,
dropdown: { x: '', y: '' },
tagsRefsIndex: 0,
@ -75,57 +77,140 @@ export default {
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 === state.routeActive;
}
};
// tagsViewList
const addBrowserSetSession = (tagsViewList: Array<object>) => {
setSession('tagsViewList', tagsViewList);
Session.set('tagsViewList', tagsViewList);
};
// vuex tagsViewRoutes
const getTagsViewRoutes = () => {
state.routePath = route.path;
const getTagsViewRoutes = async () => {
state.routeActive = await setTagsViewHighlight(route);
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
state.tagsViewList = [];
if (!store.state.themeConfig.themeConfig.isCacheTagsView) removeSession('tagsViewList');
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
initTagsView();
};
// vuex isAffix
const initTagsView = () => {
if (getSession('tagsViewList') && store.state.themeConfig.themeConfig.isCacheTagsView) {
state.tagsViewList = getSession('tagsViewList');
const initTagsView = async () => {
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
state.tagsViewList = await Session.get('tagsViewList');
} else {
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
await state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
addTagsView(route.path);
await addTagsView(route.path, route);
}
// (li)
getTagsRefsIndex(route.path);
//
tagsViewmoveToCurrentTag();
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
};
// 1 tagsViewisHide tagsView
const addTagsView = (path: string, to: any) => {
if (state.tagsViewList.some((v: any) => v.path === path)) return false;
const item = state.tagsViewRoutesList.find((v: any) => v.path === path);
if (item.meta.isLink && !item.meta.isIframe) return false;
item.query = to?.query ? to?.query : route.query;
state.tagsViewList.push({ ...item });
addBrowserSetSession(state.tagsViewList);
// 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 tagsViewisHide 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 = (path: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', path);
const refreshCurrentTagsView = (fullPath: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
};
// 3 tagsViewisAffix
const closeCurrentTagsView = (path: string) => {
state.tagsViewList.map((v: any, k: number, arr: any) => {
if (!v.meta.isAffix) {
if (v.path === path) {
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
state.tagsViewList.splice(k, 1);
setTimeout(() => {
//
if (state.tagsViewList.length === k) router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
//
else router.push({ path: arr[k].path, query: arr[k].query });
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
//
if (arr[arr.length - 1].meta.isDynamic) {
// xxx/:id/:name"
router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
} 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);
}
}
@ -138,58 +223,76 @@ export default {
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
});
addTagsView(path);
addTagsView(path, route);
};
// 5 tagsViewisAffix
const closeAllTagsView = (path: string) => {
const closeAllTagsView = () => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
state.tagsViewList.push({ ...v });
if (state.tagsViewList.some((v: any) => v.path === path)) router.push({ path, query: route.query });
else router.push({ path: v.path, query: route.query });
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
}
});
addBrowserSetSession(state.tagsViewList);
};
// 6
const openCurrenFullscreen = (path: string) => {
const item = state.tagsViewList.find((v: any) => v.path === path);
nextTick(() => {
router.push({ path, query: item.query });
const element = document.querySelector('.layout-main');
const screenfulls: any = screenfull;
screenfulls.request(element);
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 = (data: any) => {
let { id, path } = data;
let currentTag = state.tagsViewList.find((v: any) => v.path === path);
switch (id) {
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:
refreshCurrentTagsView(path);
router.push({ path, query: currentTag.query });
//
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
refreshCurrentTagsView(route.fullPath);
break;
case 1:
closeCurrentTagsView(path);
//
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
break;
case 2:
router.push({ path, query: currentTag.query });
//
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
closeOtherTagsView(path);
break;
case 3:
closeAllTagsView(path);
//
closeAllTagsView();
break;
case 4:
openCurrenFullscreen(path);
//
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
break;
}
};
//
const isActive = (path: string) => {
return path === state.routePath;
};
// x,y props
const onContextmenu = (v: any, e: any) => {
const { clientX, clientY } = e;
@ -199,10 +302,20 @@ export default {
};
// tagsView
const onTagsClick = (v: any, k: number) => {
state.routePath = v.path;
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();
@ -265,38 +378,53 @@ export default {
};
// tagsView tagsView
const getTagsRefsIndex = (path: string) => {
if (state.tagsViewList.length > 0) {
state.tagsRefsIndex = state.tagsViewList.findIndex((item: any) => item.path === path);
}
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 = () => {
const el: any = document.querySelector('.layout-navbars-tagsview-ul');
const el = document.querySelector('.layout-navbars-tagsview-ul') as HTMLElement;
if (!el) return false;
if (!getThemeConfig.value.isSortableTagsView) state.sortable && state.sortable.destroy();
if (getThemeConfig.value.isSortableTagsView) {
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-name',
onEnd: () => {
const sortEndList: any = [];
state.sortable.toArray().map((val: any) => {
state.tagsViewList.map((v: any) => {
if (v.name === val) sortEndList.push({ ...v });
});
state.sortable && state.sortable.destroy();
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-name',
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
onEnd: () => {
const sortEndList: any = [];
state.sortable.toArray().map((val: any) => {
state.tagsViewList.map((v: any) => {
if (v.name === val) sortEndList.push({ ...v });
});
addBrowserSetSession(sortEndList);
},
});
}
});
addBrowserSetSession(sortEndList);
},
});
};
// https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
const onSortableResize = () => {
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) getThemeConfig.value.isSortableTagsView = false;
else getThemeConfig.value.isSortableTagsView = true;
initSortable();
};
// tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
});
//
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);
@ -305,6 +433,19 @@ export default {
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 });
}
});
}
});
});
//
onUnmounted(() => {
@ -312,6 +453,10 @@ export default {
proxy.mittBus.off('onCurrentContextmenuClick');
// /
proxy.mittBus.off('openOrCloseSortable');
// TagsView
proxy.mittBus.off('openShareTagsView');
// resize
window.removeEventListener('resize', onSortableResize);
});
//
onBeforeUpdate(() => {
@ -324,11 +469,16 @@ export default {
initSortable();
});
//
onBeforeRouteUpdate((to) => {
state.routePath = to.path;
addTagsView(to.path, to);
getTagsRefsIndex(to.path);
tagsViewmoveToCurrentTag();
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,

View File

@ -1,7 +1,7 @@
<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">
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
@ -16,7 +16,7 @@
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank">
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>
@ -31,8 +31,8 @@
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import SubItem from '/@/views/layout/navMenu/subItem.vue';
import { useStore } from '/@/store/index';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuHorizontal',
components: { SubItem },
@ -66,15 +66,6 @@ export default defineComponent({
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
});
};
//
const setCurrentRouterHighlight = (path: string) => {
const currentPathSplit = path.split('/');
if (store.state.themeConfig.themeConfig.layout === 'classic') {
state.defaultActive = `/${currentPathSplit[1]}`;
} else {
state.defaultActive = path;
}
};
//
const filterRoutesFun = (arr: Array<object>) => {
return arr
@ -99,24 +90,36 @@ export default defineComponent({
});
return currentData;
};
//
const onHorizontalSelect = (path: string) => {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(path));
//
const setCurrentRouterHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
if (store.state.themeConfig.themeConfig.layout === 'classic') {
state.defaultActive = `/${path.split('/')[1]}`;
} else {
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;
}
};
//
onMounted(() => {
initElMenuOffsetLeft();
setCurrentRouterHighlight(route.path);
setCurrentRouterHighlight(route);
});
//
onBeforeRouteUpdate((to) => {
setCurrentRouterHighlight(to.path);
// 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,
onHorizontalSelect,
...toRefs(state),
};
},

View File

@ -13,7 +13,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank">
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>

View File

@ -3,7 +3,7 @@
router
:default-active="defaultActive"
background-color="transparent"
:collapse="setIsCollapse"
:collapse="isCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
@ -21,7 +21,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank">{{ $t(val.meta.title) }}</a></template
<a :href="val.meta.isLink" target="_blank" rel="opener">{{ $t(val.meta.title) }}</a></template
>
</el-menu-item>
</template>
@ -29,10 +29,10 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance } from 'vue';
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import SubItem from '/@/views/layout/navMenu/subItem.vue';
import { useStore } from '/@/store/index';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuVertical',
components: { SubItem },
@ -47,7 +47,9 @@ export default defineComponent({
const store = useStore();
const route = useRoute();
const state = reactive({
defaultActive: route.path,
// https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
//
const menuLists = computed(() => {
@ -57,13 +59,31 @@ export default defineComponent({
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;
};
// /
const setIsCollapse = computed(() => {
return document.body.clientWidth < 1000 ? false : getThemeConfig.value.isCollapse;
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) => {
state.defaultActive = to.path;
// 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;
@ -71,7 +91,6 @@ export default defineComponent({
return {
menuLists,
getThemeConfig,
setIsCollapse,
...toRefs(state),
};
},

View File

@ -0,0 +1,60 @@
<template>
<div class="layout-view-bg-white flex" :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 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',
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({
iframeLoading: true,
iframeUrl: '',
});
// 初始化页面加载 loading
const initIframeLoad = () => {
state.iframeUrl = route.meta.isLink;
nextTick(() => {
state.iframeLoading = true;
const iframe = document.getElementById('iframe');
if (!iframe) return false;
iframe.onload = () => {
state.iframeLoading = false;
};
});
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `0px`;
} else {
if (isTagsview) return `84px`;
else return `50px`;
}
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
watch(
() => route.path,
() => {
initIframeLoad();
}
);
return {
setIframeHeight,
...toRefs(state),
};
},
});
</script>

View File

@ -0,0 +1,43 @@
<template>
<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 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',
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,
}
);
return {
setLinkHeight,
...toRefs(state),
};
},
});
</script>

View File

@ -11,9 +11,9 @@
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick } from 'vue';
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index.ts';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutParentView',
setup() {
@ -40,12 +40,11 @@ export default defineComponent({
//
onBeforeMount(() => {
state.keepAliveNameList = getKeepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
if (route.path !== path) return false;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = route.path;
state.refreshRouterViewKey = null;
nextTick(() => {
state.refreshRouterViewKey = null;
state.refreshRouterViewKey = fullPath;
state.keepAliveNameList = getKeepAliveNames.value;
});
});
@ -54,6 +53,13 @@ export default defineComponent({
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView');
});
// tagsView
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = route.fullPath;
}
);
return {
getThemeConfig,
getKeepAliveNames,

View File

@ -2,17 +2,27 @@ import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { store, key } from './store';
import { authDirective } from '/@/utils/authDirective.ts';
import { i18n } from '/@/i18n/index.ts';
import { directive } from '/@/utils/directive';
import { i18n } from '/@/i18n/index';
import { globalComponentSize } from '/@/utils/componentSize';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/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);
app.use(router).use(store, key).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(screenShort, { enableWebRtc: false }).mount('#app');
app
.use(router)
.use(store, key)
.use(ElementPlus, { i18n: i18n.global.t, size: globalComponentSize })
.use(i18n)
.use(screenShort, { enableWebRtc: false })
.use(VueGridLayout)
.mount('#app');
app.config.globalProperties.mittBus = mitt();
authDirective(app);
directive(app);

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.authPageList[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();
}

File diff suppressed because it is too large Load Diff

1098
src/router/route.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,26 @@
import { InjectionKey } from 'vue';
import { createStore, useStore as baseUseStore, Store } from 'vuex';
import themeConfig from '/@/store/modules/themeConfig.ts';
import routesList from '/@/store/modules/routesList.ts';
import keepAliveNames from '/@/store/modules/keepAliveNames.ts';
import tagsViewRoutes from '/@/store/modules/tagsViewRoutes.ts';
import userInfos from '/@/store/modules/userInfos.ts';
import requestOldRoutes from '/@/store/modules/requestOldRoutes.ts';
import { RootStateTypes } from '/@/store/interface/index';
// Vite supports importing multiple modules from the file system using the special import.meta.glob function
// see https://cn.vitejs.dev/guide/features.html#glob-import
const modulesFiles = import.meta.globEager('./modules/*.ts');
const pathList: string[] = [];
for (const path in modulesFiles) {
pathList.push(path);
}
const modules = pathList.reduce((modules: { [x: string]: any }, modulePath: string) => {
const moduleName = modulePath.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
const value = modulesFiles[modulePath];
modules[moduleName] = value.default;
return modules;
}, {});
export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
export const store = createStore<RootStateTypes>({
modules: {
themeConfig,
routesList,
keepAliveNames,
tagsViewRoutes,
userInfos,
requestOldRoutes,
},
});
export const store = createStore<RootStateTypes>({ modules });
export function useStore() {
return baseUseStore(key);

View File

@ -1,7 +1,7 @@
// 接口类型声明
// 布局配置
declare interface ThemeConfigState {
export interface ThemeConfigState {
themeConfig: {
isDrawer: boolean;
primary: string;
@ -17,6 +17,7 @@ declare interface ThemeConfigState {
columnsMenuBarColor: string;
isTopBarColorGradual: boolean;
isMenuBarColorGradual: boolean;
isColumnsMenuBarColorGradual: boolean;
isMenuBarColorHighlight: boolean;
isCollapse: boolean;
isUniqueOpened: boolean;
@ -33,6 +34,7 @@ declare interface ThemeConfigState {
isTagsviewIcon: boolean;
isCacheTagsView: boolean;
isSortableTagsView: boolean;
isShareTagsView: boolean;
isFooter: boolean;
isGrayscale: boolean;
isInvert: boolean;
@ -41,41 +43,44 @@ declare interface ThemeConfigState {
tagsStyle: string;
animation: string;
columnsAsideStyle: string;
columnsAsideLayout: string;
layout: string;
isRequestRoutes: boolean;
globalTitle: string;
globalViceTitle: string;
globalI18n: string;
globalComponentSize: string;
};
}
// 路由列表
declare interface RoutesListState {
export interface RoutesListState {
routesList: Array<object>;
}
// 路由缓存列表
declare interface KeepAliveNamesState {
export interface KeepAliveNamesState {
keepAliveNames: Array<string>;
}
// TagsView 路由列表
declare interface TagsViewRoutesState {
export interface TagsViewRoutesState {
tagsViewRoutes: Array<object>;
isTagsViewCurrenFull: Boolean;
}
// 用户信息
declare interface UserInfosState {
export interface UserInfosState {
userInfos: object;
}
// 后端返回原始路由(未处理时)
declare interface RequestOldRoutesState {
export interface RequestOldRoutesState {
requestOldRoutes: Array<object>;
}
// 主接口(顶级类型声明)
declare interface RootStateTypes {
export interface RootStateTypes {
themeConfig: ThemeConfigState;
routesList: RoutesListState;
keepAliveNames: KeepAliveNamesState;

View File

@ -1,4 +1,6 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { KeepAliveNamesState, RootStateTypes } from '/@/store/interface/index';
const keepAliveNamesModule: Module<KeepAliveNamesState, RootStateTypes> = {
namespaced: true,

View File

@ -1,4 +1,6 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { RequestOldRoutesState, RootStateTypes } from '/@/store/interface/index';
const requestOldRoutesModule: Module<RequestOldRoutesState, RootStateTypes> = {
namespaced: true,

View File

@ -1,4 +1,6 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { RoutesListState, RootStateTypes } from '/@/store/interface/index';
const routesListModule: Module<RoutesListState, RootStateTypes> = {
namespaced: true,

View File

@ -1,21 +1,32 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index';
const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
namespaced: true,
state: {
tagsViewRoutes: [],
isTagsViewCurrenFull: false,
},
mutations: {
// 设置 TagsView 路由
getTagsViewRoutes(state: any, data: Array<string>) {
state.tagsViewRoutes = data;
},
// 设置卡片全屏
getCurrenFullscreen(state: any, bool: boolean) {
state.isTagsViewCurrenFull = bool;
},
},
actions: {
// 设置 TagsView 路由
async setTagsViewRoutes({ commit }, data: Array<string>) {
commit('getTagsViewRoutes', data);
},
// 设置卡片全屏
setCurrenFullscreen({ commit }, bool: Boolean) {
commit('getCurrenFullscreen', bool);
},
},
};

View File

@ -1,5 +1,12 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { ThemeConfigState, RootStateTypes } from '/@/store/interface/index';
/**
* 2020.05.28 by lyt 优化
* 修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效
* 哪个大佬有解决办法欢迎pr感谢💕
*/
const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
namespaced: true,
state: {
@ -7,8 +14,9 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
// 是否开启布局配置抽屉
isDrawer: false,
/* 全局主题
------------------------------- */
/**
* 全局主题
*/
// 默认 primary 颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
primary: '#409eff',
// 默认 success 颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
@ -20,8 +28,12 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
// 默认 danger 颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
danger: '#f56c6c',
/* 菜单 / 顶栏
------------------------------- */
/**
* 菜单 / 顶栏
* 注意v1.0.17 版本去除设置布局切换重置主题样式initSetLayoutChange
* 切换布局需手动设置样式,设置的样式自动同步各布局,
* 代码位置:/@/layout/navBars/breadcrumb/setings.vue
*/
// 默认顶栏导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
topBar: '#ffffff',
// 默认菜单导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
@ -38,12 +50,15 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
isTopBarColorGradual: false,
// 是否开启菜单背景颜色渐变
isMenuBarColorGradual: false,
// 是否开启分栏菜单背景颜色渐变
isColumnsMenuBarColorGradual: false,
// 是否开启菜单字体背景高亮
isMenuBarColorHighlight: false,
// 是否开启菜单字体背景高亮
/* 界面设置
------------------------------- */
/**
* 界面设置
*/
// 是否开启菜单水平折叠效果
isCollapse: false,
// 是否开启菜单手风琴效果
@ -59,13 +74,14 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
// 开启自动锁屏倒计时(s/秒)
lockScreenTime: 30,
/* 界面显示
------------------------------- */
/**
* 界面显示
*/
// 是否开启侧边栏 Logo
isShowLogo: false,
// 初始化变量,用于 el-scrollbar 的高度更新,请勿删除
isShowLogoChange: false,
// 是否开启 Breadcrumb
// 是否开启 Breadcrumb,强制经典、横向布局不显示
isBreadcrumb: true,
// 是否开启 Tagsview
isTagsview: true,
@ -77,6 +93,8 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
isCacheTagsView: false,
// 是否开启 TagsView 拖拽
isSortableTagsView: true,
// 是否开启 TagsView 共用
isShareTagsView: false,
// 是否开启 Footer 底部版权信息
isFooter: false,
// 是否开启灰色模式
@ -88,33 +106,44 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
// 水印文案
wartermarkText: 'small@小柒',
/* 其它设置
------------------------------- */
// 默认 Tagsview 风格,可选 1、 tags-style-one 2、 tags-style-two 3、 tags-style-three 4、 tags-style-four
/**
* 其它设置
*/
// Tagsview 风格:可选值"<tags-style-one|tags-style-two|tags-style-three|tags-style-four>",默认 tags-style-one
// 定义的值与 `/src/layout/navBars/tagsView/tagsView.vue` 中的 class 同名
tagsStyle: 'tags-style-one',
// 默认主页面切换动画可选 1、 slide-right 2、 slide-left 3、 opacitys
// 主页面切换动画可选值"<slide-right|slide-left|opacitys>",默认 slide-right
animation: 'slide-right',
// 默认分栏高亮风格可选 1、 圆角 columns-round 2、 卡片 columns-card
// 分栏高亮风格可选值"<columns-round|columns-card>",默认 columns-round
columnsAsideStyle: 'columns-round',
// 分栏布局风格:可选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
columnsAsideLayout: 'columns-vertical',
/* 布局切换
------------------------------- */
// 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
/**
* 布局切换
* 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/breadcrumb/setings.vue
* 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法
*/
// 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults
layout: 'defaults',
/* 后端控制路由
------------------------------- */
/**
* 后端控制路由
*/
// 是否开启后端控制路由
isRequestRoutes: false,
/* 全局网站标题 / 副标题
------------------------------- */
/**
* 全局网站标题 / 副标题
*/
// 网站主标题(菜单导航、浏览器当前网页标题)
globalTitle: 'vue-next-admin',
// 网站副标题(登录页顶部文字)
globalViceTitle: 'SMALL@小柒',
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
globalI18n: 'zh-cn',
// 默认全局组件大小,可选值"<|medium|small|mini>",默认 ''
globalComponentSize: '',
},
},
mutations: {

View File

@ -1,5 +1,7 @@
import { Module } from 'vuex';
import { getSession } from '/@/utils/storage.ts';
import { Session } from '/@/utils/storage';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { UserInfosState, RootStateTypes } from '/@/store/interface/index';
const userInfosModule: Module<UserInfosState, RootStateTypes> = {
namespaced: true,
@ -18,7 +20,7 @@ const userInfosModule: Module<UserInfosState, RootStateTypes> = {
if (data) {
commit('getUserInfos', data);
} else {
if (getSession('userInfo')) commit('getUserInfos', getSession('userInfo'));
if (Session.get('userInfo')) commit('getUserInfos', Session.get('userInfo'));
}
},
},

View File

@ -54,6 +54,7 @@ body,
.el-scrollbar {
width: 100%;
}
// 此字段多次用到,建议不删除,如需修改,请重写覆盖样式
.layout-view-bg-white {
background: white;
width: 100%;
@ -64,18 +65,44 @@ body,
.layout-el-aside-br-color {
border-right: 1px solid rgb(238, 238, 238);
}
.layout-aside-width-default {
// pc端左侧导航样式
.layout-aside-pc-220 {
width: 220px !important;
transition: width 0.3s ease;
}
.layout-aside-width64 {
.layout-aside-pc-64 {
width: 64px !important;
transition: width 0.3s ease;
}
.layout-aside-width1 {
.layout-aside-pc-1 {
width: 1px !important;
transition: width 0.3s ease;
}
// 手机端左侧导航样式
.layout-aside-mobile {
position: fixed;
top: 0;
left: -220px;
width: 220px;
z-index: 1;
}
.layout-aside-mobile-close {
left: -220px;
transition: all 0.3s cubic-bezier(0.71, -0.01, 0.18, 0.95);
}
.layout-aside-mobile-open {
left: 0;
transition: all 0.3s cubic-bezier(0.53, -0.26, 0.42, 1.18);
}
.layout-aside-mobile-mode {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.layout-scrollbar {
@extend .el-scrollbar;
padding: 15px;

View File

@ -1,5 +1,6 @@
@import 'mixins/function.scss';
@import 'mixins/element-mixins.scss';
@import 'mixins/mixins.scss';
/* Button 按钮
------------------------------- */
@ -583,6 +584,25 @@
background-color: set-color(info);
}
/* Result 结果
------------------------------- */
// success
.el-result .icon-success {
fill: set-color(success);
}
// warning
.el-result .icon-warning {
fill: set-color(warning);
}
// error
.el-result .icon-error {
fill: set-color(danger);
}
// info
.el-result .icon-info {
fill: set-color(info);
}
/* Alert 警告
------------------------------- */
// success
@ -727,6 +747,11 @@
// 默认样式修改
.el-menu {
border-right: none !important;
width: 220px;
}
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
.el-menu--collapse {
width: 64px !important;
}
.el-menu-item,
.el-submenu__title {
@ -740,7 +765,7 @@
.el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
border-bottom: 3px solid !important;
border-bottom-color: set-color(primary);
color: set-color(primary);
color: set-color(primary) !important;
}
.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,
.el-menu--horizontal .el-menu-item:not(.is-disabled):hover,
@ -748,7 +773,7 @@
.el-menu--horizontal > .el-submenu:hover .el-submenu__title,
.el-menu--horizontal .el-menu .el-menu-item.is-active,
.el-menu--horizontal .el-menu .el-submenu.is-active > .el-submenu__title {
color: set-color(primary);
color: set-color(primary) !important;
}
.el-menu.el-menu--horizontal {
border-bottom: none !important;
@ -778,8 +803,9 @@
color: set-color(primary);
}
}
// 高亮时
.el-menu-item.is-active {
// 高亮时/菜单收起时
.el-menu-item.is-active,
.el-menu--collapse .el-submenu.is-active i {
color: set-color(primary);
}
.el-active-extend {
@ -816,12 +842,11 @@
// 第三方图标字体间距/大小设置
.el-menu-item .iconfont,
.el-submenu .iconfont {
font-size: 14px !important;
display: inline-block;
vertical-align: middle;
margin-right: 5px;
width: 24px;
text-align: center;
@include generalIcon;
}
.el-menu-item .fa,
.el-submenu .fa {
@include generalIcon;
}
// element plus 本身字体图标
.el-submenu [class^='el-icon-'] {
@ -866,6 +891,9 @@
color: set-color(primary);
background-color: set-color(primary-light-9);
}
.el-dropdown-menu .el-dropdown-menu__item {
white-space: nowrap;
}
/* Steps 步骤条
------------------------------- */
@ -914,13 +942,14 @@
justify-content: center;
.el-dialog {
margin: 0 auto !important;
position: absolute;
.el-dialog__body {
padding: 20px !important;
}
}
}
.el-dialog__body {
max-height: 70vh !important;
max-height: calc(90vh - 111px) !important;
overflow-y: auto;
overflow-x: hidden;
}
@ -986,6 +1015,9 @@
.el-select-dropdown__wrap {
max-height: 274px !important; /*修复Select 选择器高度问题*/
}
.el-cascader-menu__wrap.el-scrollbar__wrap {
height: 204px !important; /*修复Cascader 级联选择器高度问题*/
}
/* Drawer 抽屉
------------------------------- */

View File

@ -3,51 +3,49 @@
.icon-selector-popper {
padding: 0 !important;
.icon-selector-warp {
height: 260px;
overflow: hidden;
.icon-selector-warp-title {
height: 40px;
line-height: 40px;
padding: 0 15px;
}
.icon-selector-warp-row {
max-height: 260px;
overflow-y: auto;
padding: 15px 15px 5px;
height: 230px;
overflow: hidden;
border-top: 1px solid #ebeef5;
.ele-col:nth-last-child(1),
.ele-col:nth-last-child(2) {
display: none;
.el-row {
padding: 15px;
}
.awe-col:nth-child(-n + 24) {
.el-scrollbar__bar.is-horizontal {
display: none;
}
.icon-selector-warp-item {
display: flex;
border: 1px solid #ebeef5;
padding: 10px;
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
transition: all 0.3s ease;
.icon-selector-warp-item-value {
transition: all 0.3s ease;
i {
font-size: 20px;
color: #606266;
}
}
&:hover {
border: 1px solid var(--color-primary);
cursor: pointer;
transition: all 0.3s ease;
background-color: var(--color-primary-light-9);
border: 1px solid var(--color-primary-light-6);
.icon-selector-warp-item-value {
i {
color: var(--color-primary);
transition: all 0.3s ease;
}
}
}
}
.icon-selector-active {
border: 1px solid var(--color-primary);
background-color: var(--color-primary-light-9);
border: 1px solid var(--color-primary-light-6);
.icon-selector-warp-item-value {
i {
color: var(--color-primary);
@ -55,33 +53,5 @@
}
}
}
.icon-selector-all {
.el-input {
padding: 0 15px;
margin-bottom: 10px;
}
&-tabs {
display: flex;
height: 30px;
line-height: 30px;
padding: 0 15px;
margin-bottom: 5px;
&-item {
flex: 1;
text-align: center;
cursor: pointer;
&:hover {
color: var(--color-primary);
}
}
&-active {
background: var(--color-primary);
border-radius: 5px;
.label {
color: #ffffff;
}
}
}
}
}
}

View File

@ -4,3 +4,4 @@
@import './element.scss';
@import './iconSelector.scss';
@import './media/media.scss';
@import './waves.scss';

View File

@ -10,4 +10,7 @@
.el-form-item__content {
margin-left: 0 !important;
}
.el-form-item {
display: unset !important;
}
}

View File

@ -4,18 +4,23 @@
------------------------------- */
@media screen and (max-width: $xs) {
.login-container {
.login-content {
width: 90% !important;
padding: 20px 0 !important;
background: none !important;
.login-logo {
display: none;
}
.login-content-form-btn {
.login-content {
width: 100% !important;
padding: 12px 0 !important;
height: 100% !important;
padding: 20px 0 !important;
border-radius: 0 !important;
box-shadow: unset !important;
border: none !important;
}
.login-copyright {
.login-copyright-msg {
white-space: unset !important;
}
display: none !important;
}
.el-form-item {
display: flex !important;
}
}
}

View File

@ -1,3 +1,14 @@
/* 第三方图标字体间距/大小设置
------------------------------- */
@mixin generalIcon {
font-size: 14px !important;
display: inline-block;
vertical-align: middle;
margin-right: 5px;
width: 24px;
text-align: center;
}
/* 文本不换行
------------------------------- */
@mixin text-no-wrap() {

101
src/theme/waves.scss Normal file
View File

@ -0,0 +1,101 @@
/* Waves v0.6.0
* http://fian.my.id/Waves
*
* Copyright 2014 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE
*/
.waves-effect {
position: relative;
cursor: pointer;
display: inline-block;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
vertical-align: middle;
z-index: 1;
will-change: opacity, transform;
transition: all 0.3s ease-out;
}
.waves-effect .waves-ripple {
position: absolute;
border-radius: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
opacity: 0;
background: rgba(0, 0, 0, 0.2);
transition: all 0.7s ease-out;
transition-property: opacity, -webkit-transform;
transition-property: transform, opacity;
transition-property: transform, opacity, -webkit-transform;
-webkit-transform: scale(0);
transform: scale(0);
pointer-events: none;
}
.waves-effect.waves-light .waves-ripple {
background-color: rgba(255, 255, 255, 0.45);
}
.waves-effect.waves-red .waves-ripple {
background-color: rgba(244, 67, 54, 0.7);
}
.waves-effect.waves-yellow .waves-ripple {
background-color: rgba(255, 235, 59, 0.7);
}
.waves-effect.waves-orange .waves-ripple {
background-color: rgba(255, 152, 0, 0.7);
}
.waves-effect.waves-purple .waves-ripple {
background-color: rgba(156, 39, 176, 0.7);
}
.waves-effect.waves-green .waves-ripple {
background-color: rgba(76, 175, 80, 0.7);
}
.waves-effect.waves-teal .waves-ripple {
background-color: rgba(0, 150, 136, 0.7);
}
.waves-effect input[type='button'],
.waves-effect input[type='reset'],
.waves-effect input[type='submit'] {
border: 0;
font-style: normal;
font-size: inherit;
text-transform: inherit;
background: none;
}
.waves-notransition {
transition: none !important;
}
.waves-circle {
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-mask-image: -webkit-radial-gradient(circle, #fff 100%, #000 100%);
}
.waves-input-wrapper {
border-radius: 0.2em;
vertical-align: bottom;
}
.waves-input-wrapper .waves-button-input {
position: relative;
top: 0;
left: 0;
z-index: 1;
}
.waves-circle {
text-align: center;
width: 2.5em;
height: 2.5em;
line-height: 2.5em;
border-radius: 50%;
-webkit-mask-image: none;
}
.waves-block {
display: block;
}
a.waves-effect .waves-ripple {
z-index: -1;
}

View File

@ -1,5 +1,10 @@
// 判断两数组是否相同
export function judementSameArr(news: Array<string>, old: Array<string>) {
/**
* 判断两数组是否相同
* @param news 新数据
* @param old 源数据
* @returns 两数组相同返回 `true`,反之则反
*/
export function judementSameArr(news: Array<string>, old: Array<string>): boolean {
let count = 0;
const leng = old.length;
for (let i in old) {
@ -9,3 +14,28 @@ export function judementSameArr(news: Array<string>, old: Array<string>) {
}
return count === leng ? true : false;
}
/**
* 判断两个对象是否相同
* @param a 要比较的对象一
* @param b 要比较的对象二
* @returns 相同返回 true反之则反
*/
export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]: any }) {
if (!a || !b) return false;
let aProps = Object.getOwnPropertyNames(a);
let bProps = Object.getOwnPropertyNames(b);
if (aProps.length != bProps.length) return false;
for (let i = 0; i < aProps.length; i++) {
let propName = aProps[i];
let propA = a[propName];
let propB = b[propName];
if (!b.hasOwnProperty(propName)) return false;
if (propA instanceof Object) {
if (!isObjectValueEqual(propA, propB)) return false;
} else if (propA !== propB) {
return false;
}
}
return true;
}

View File

@ -1,20 +1,26 @@
import type { App } from 'vue';
import { store } from '/@/store/index.ts';
import { judementSameArr } from '/@/utils/arrayOperation.ts';
import { judementSameArr } from '/@/utils/arrayOperation';
/**
* 用户权限指令
* @directive 单个权限验证v-auth="xxx"
* @directive 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
* @directive 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
*/
export function authDirective(app: App) {
// 单个权限验证v-auth="xxx"
app.directive('auth', {
mounted(el, binding) {
if (!store.state.userInfos.userInfos.authBtnList.some((v: any) => v === binding.value)) el.parentNode.removeChild(el);
if (!store.state.userInfos.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
},
});
// 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
app.directive('auths', {
mounted(el, binding) {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: any) => {
binding.value.map((v: any) => {
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
binding.value.map((v: string) => {
if (val === v) flag = true;
});
});

View File

@ -1,23 +1,35 @@
import { store } from '/@/store/index.ts';
import { judementSameArr } from '/@/utils/arrayOperation.ts';
import { judementSameArr } from '/@/utils/arrayOperation';
// 单个权限验证
export function auth(value: string) {
return store.state.userInfos.userInfos.authBtnList.some((v: any) => v === value);
/**
* 单个权限验证
* @param value 权限值
* @returns 有权限,返回 `true`,反之则反
*/
export function auth(value: string): boolean {
return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === value);
}
// 多个权限验证,满足一个则为 true
export function auths(value: Array<string>) {
/**
* 多个权限验证,满足一个则为 true
* @param value 权限值
* @returns 有权限,返回 `true`,反之则反
*/
export function auths(value: Array<string>): boolean {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: any) => {
value.map((v: any) => {
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
value.map((v: string) => {
if (val === v) flag = true;
});
});
return flag;
}
// 多个权限验证,全部满足则为 true
export function authAll(value: Array<string>) {
/**
* 多个权限验证,全部满足则为 true
* @param value 权限值
* @returns 有权限,返回 `true`,反之则反
*/
export function authAll(value: Array<string>): boolean {
return judementSameArr(value, store.state.userInfos.userInfos.authBtnList);
}

View File

@ -0,0 +1,65 @@
// 通用函数
import useClipboard from 'vue-clipboard3';
import { ElMessage } from 'element-plus';
import { formatDate } from '/@/utils/formatTime';
import { useI18n } from 'vue-i18n';
export default function () {
const { t } = useI18n();
const { toClipboard } = useClipboard();
//百分比格式化
const percentFormat = (row: any, column: number, cellValue: any) => {
return cellValue ? `${cellValue}%` : '-';
};
//列表日期时间格式化
const dateFormatYMD = (row: any, column: number, cellValue: any) => {
if (!cellValue) return '-';
return formatDate(new Date(cellValue), 'YYYY-mm-dd');
};
//列表日期时间格式化
const dateFormatYMDHMS = (row: any, column: number, cellValue: any) => {
if (!cellValue) return '-';
return formatDate(new Date(cellValue), 'YYYY-mm-dd HH:MM:SS');
};
//列表日期时间格式化
const dateFormatHMS = (row: any, column: number, cellValue: any) => {
if (!cellValue) return '-';
let time = 0;
if (typeof row === 'number') time = row;
if (typeof cellValue === 'number') time = cellValue;
return formatDate(new Date(time * 1000), 'HH:MM:SS');
};
// 小数格式化
const scaleFormat = (value: any = 0, scale: number = 4) => {
return Number.parseFloat(value).toFixed(scale);
};
// 小数格式化
const scale2Format = (value: any = 0) => {
return Number.parseFloat(value).toFixed(2);
};
// 点击复制文本
const copyText = (text: string) => {
return new Promise((resolve, reject) => {
try {
//复制
toClipboard(text);
//下面可以设置复制成功的提示框等操作
ElMessage.success(t('message.layout.copyTextSuccess'));
resolve(text);
} catch (e) {
//复制失败
ElMessage.error(t('message.layout.copyTextError'));
reject(e);
}
});
};
return {
percentFormat,
dateFormatYMD,
dateFormatYMDHMS,
dateFormatHMS,
scaleFormat,
scale2Format,
copyText,
};
}

View File

@ -0,0 +1,7 @@
import { Local } from '/@/utils/storage';
/**
* 全局组件大小
* @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
*/
export const globalComponentSize: string = Local.get('themeConfig')?.globalComponentSize;

View File

@ -0,0 +1,178 @@
import type { App } from 'vue';
/**
* 按钮波浪指令
* @directive 默认方式v-waves如 `<div v-waves></div>`
* @directive 参数方式v-waves=" |light|red|orange|purple|green|teal",如 `<div v-waves="'light'"></div>`
*/
export function wavesDirective(app: App) {
app.directive('waves', {
mounted(el, binding) {
el.classList.add('waves-effect');
binding.value && el.classList.add(`waves-${binding.value}`);
function setConvertStyle(obj: { [key: string]: unknown }) {
let style: string = '';
for (let i in obj) {
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
}
return style;
}
function onCurrentClick(e: { [key: string]: unknown }) {
let elDiv = document.createElement('div');
elDiv.classList.add('waves-ripple');
el.appendChild(elDiv);
let styles = {
left: `${e.layerX}px`,
top: `${e.layerY}px`,
opacity: 1,
transform: `scale(${(el.clientWidth / 100) * 10})`,
'transition-duration': `750ms`,
'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`,
};
elDiv.setAttribute('style', setConvertStyle(styles));
setTimeout(() => {
elDiv.setAttribute(
'style',
setConvertStyle({
opacity: 0,
transform: styles.transform,
left: styles.left,
top: styles.top,
})
);
setTimeout(() => {
elDiv && el.removeChild(elDiv);
}, 750);
}, 450);
}
el.addEventListener('mousedown', onCurrentClick, false);
},
unmounted(el) {
el.addEventListener('mousedown', () => {});
},
});
}
/**
* 自定义拖动指令
* @description 使用方式v-drag="[dragDom,dragHeader]",如 `<div v-drag="['.drag-container .el-dialog', '.drag-container .el-dialog__header']"></div>`
* @description dragDom 要拖动的元素dragHeader 要拖动的 Header 位置
* @link 注意https://github.com/element-plus/element-plus/issues/522
* @lick 参考https://blog.csdn.net/weixin_46391323/article/details/105228020?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-10&spm=1001.2101.3001.4242
*/
export function dragDirective(app: App) {
app.directive('drag', {
mounted(el, binding) {
if (!binding.value) return false;
const dragDom = document.querySelector(binding.value[0]) as HTMLElement;
const dragHeader = document.querySelector(binding.value[1]) as HTMLElement;
dragHeader.onmouseover = () => (dragHeader.style.cursor = `move`);
function down(e: any, type: string) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = type === 'pc' ? e.clientX - dragHeader.offsetLeft : e.touches[0].clientX - dragHeader.offsetLeft;
const disY = type === 'pc' ? e.clientY - dragHeader.offsetTop : e.touches[0].clientY - dragHeader.offsetTop;
// body当前宽度
const screenWidth = document.body.clientWidth;
// 可见区域高度(应为body高度可某些环境下无法获取)
const screenHeight = document.documentElement.clientHeight;
// 对话框宽度
const dragDomWidth = dragDom.offsetWidth;
// 对话框高度
const dragDomheight = dragDom.offsetHeight;
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL: any = getComputedStyle(dragDom).left;
let styT: any = getComputedStyle(dragDom).top;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
}
return {
disX,
disY,
minDragDomLeft,
maxDragDomLeft,
minDragDomTop,
maxDragDomTop,
styL,
styT,
};
}
function move(e: any, type: string, obj: any) {
let { disX, disY, minDragDomLeft, maxDragDomLeft, minDragDomTop, maxDragDomTop, styL, styT } = obj;
// 通过事件委托,计算移动的距离
let left = type === 'pc' ? e.clientX - disX : e.touches[0].clientX - disX;
let top = type === 'pc' ? e.clientY - disY : e.touches[0].clientY - disY;
// 边界处理
if (-left > minDragDomLeft) {
left = -minDragDomLeft;
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft;
}
if (-top > minDragDomTop) {
top = -minDragDomTop;
} else if (top > maxDragDomTop) {
top = maxDragDomTop;
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
}
/**
* pc端
* onmousedown 鼠标按下触发事件
* onmousemove 鼠标按下时持续触发事件
* onmouseup 鼠标抬起触发事件
*/
dragHeader.onmousedown = (e) => {
const obj = down(e, 'pc');
document.onmousemove = (e) => {
move(e, 'pc', obj);
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
};
/**
* 移动端
* ontouchstart 当按下手指时触发ontouchstart
* ontouchmove 当移动手指时触发ontouchmove
* ontouchend 当移走手指时触发ontouchend
*/
dragHeader.ontouchstart = (e) => {
const obj = down(e, 'app');
document.ontouchmove = (e) => {
move(e, 'app', obj);
};
document.ontouchend = () => {
document.ontouchmove = null;
document.ontouchend = null;
};
};
},
});
}

21
src/utils/deepClone.ts Normal file
View File

@ -0,0 +1,21 @@
/**
* 对象深克隆
* @param obj 源对象
* @returns 克隆后的对象
*/
export function deepClone(obj: any) {
let newObj: any;
try {
newObj = obj.push ? [] : {};
} catch (error) {
newObj = {};
}
for (let attr in obj) {
if (typeof obj[attr] === 'object') {
newObj[attr] = deepClone(obj[attr]);
} else {
newObj[attr] = obj[attr];
}
}
return newObj;
}

18
src/utils/directive.ts Normal file
View File

@ -0,0 +1,18 @@
import type { App } from 'vue';
import { authDirective } from '/@/utils/authDirective';
import { wavesDirective, dragDirective } from '/@/utils/customDirective';
/**
* 导出指令方法v-xxx
* @methods authDirective 用户权限指令用法v-auth
* @methods wavesDirective 按钮波浪指令用法v-waves
* @methods dragDirective 自定义拖动指令用法v-drag
*/
export function directive(app: App) {
// 用户权限指令
authDirective(app);
// 按钮波浪指令
wavesDirective(app);
// 自定义拖动指令
dragDirective(app);
}

View File

@ -1,20 +1,19 @@
/*
* 年(Y) 可用1-4个占位符
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
* 星期(W) 可用1-3个占位符
* 季度(q为阿拉伯数字Q为中文数字)可用1或4个占位符
*
* let date = new Date()
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
/**
* 时间日期转换
* @param date 当前时间new Date() 格式
* @param format 需要转换的时间格式字符串
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串
*/
export function formatDate(date: Date, format: string) {
export function formatDate(date: Date, format: string): string {
let we = date.getDay(); // 星期
let z = getWeek(date); // 周
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
const opt: any = {
const opt: { [key: string]: string } = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始要+1)
'd+': date.getDate().toString(), // 日
@ -24,7 +23,7 @@ export function formatDate(date: Date, format: string) {
'q+': qut, // 季度
};
// 中文数字 (星期)
const week: any = {
const week: { [key: string]: string } = {
'0': '日',
'1': '一',
'2': '二',
@ -34,7 +33,7 @@ export function formatDate(date: Date, format: string) {
'6': '六',
};
// 中文数字(季度)
const quarter: any = {
const quarter: { [key: string]: string } = {
'1': '一',
'2': '二',
'3': '三',
@ -43,6 +42,7 @@ export function formatDate(date: Date, format: string) {
if (/(W+)/.test(format))
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
if (/(Z+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '');
for (let k in opt) {
let r = new RegExp('(' + k + ')').exec(format);
// 若输入的长度不为1则前面补零
@ -52,27 +52,42 @@ export function formatDate(date: Date, format: string) {
}
/**
* 10秒 10 * 1000
* 1分 60 * 1000
* 1小时 60 * 60 * 1000
* 24小时60 * 60 * 24 * 1000
* 3天 60 * 60* 24 * 1000 * 3
*
* let data = new Date()
* formatPast(data) // 刚刚
* formatPast(data - 11 * 1000) // 11秒前
* formatPast(data - 2 * 60 * 1000) // 2分钟前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
* formatPast("2020-06-01") // 2020-06-01
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
* 获取当前日期是第几周
* @param dateTime 当前传入的日期值
* @returns 返回第几周数字值
*/
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
export function getWeek(dateTime: Date): number {
let temptTime = new Date(dateTime.getTime());
// 周几
let weekday = temptTime.getDay() || 7;
// 周1+5天=周六
temptTime.setDate(temptTime.getDate() - weekday + 1 + 5);
let firstDay = new Date(temptTime.getFullYear(), 0, 1);
let dayOfWeek = firstDay.getDay();
let spendDay = 1;
if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1;
firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay);
let d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000);
let result = Math.ceil(d / 7);
return result;
}
/**
* 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
* @param param 当前时间new Date() 格式或者字符串时间格式
* @param format 需要转换的时间格式字符串
* @description param 10秒 10 * 1000
* @description param 1分 60 * 1000
* @description param 1小时 60 * 60 * 1000
* @description param 24小时60 * 60 * 24 * 1000
* @description param 3天 60 * 60* 24 * 1000 * 3
* @returns 返回拼接后的时间字符串
*/
export function formatPast(param: string | Date, format: string = 'YYYY-mm-dd'): string {
// 传入格式处理、存储转换值
let t: any, s: any;
let t: any, s: number;
// 获取js 时间戳
let time: any = new Date().getTime();
let time: number = new Date().getTime();
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
// 当前时间戳 - 传入时间戳
@ -104,9 +119,12 @@ export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
}
/**
* formatAxis(new Date()) // 上午好
* 时间问候语
* @param param 当前时间new Date() 格式
* @description param 调用 `formatAxis(new Date())` 输出 `上午好`
* @returns 返回拼接后的时间字符串
*/
export function formatAxis(param: any) {
export function formatAxis(param: Date): string {
let hour: number = new Date(param).getHours();
if (hour < 6) return '凌晨好';
else if (hour < 9) return '早上好';

View File

@ -28,7 +28,7 @@ const getAlicdnIconfont = () => {
};
// 初始化获取 css 样式,获取 element plus 自带图标
const elementPlusIconfont = () => {
const getElementPlusIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const styles: any = document.styleSheets;
@ -36,20 +36,22 @@ const elementPlusIconfont = () => {
for (let i = 0; i < styles.length; i++) {
for (let j = 0; j < styles[i].cssRules.length; j++) {
if (styles[i].cssRules[j].selectorText && styles[i].cssRules[j].selectorText.indexOf('.el-icon-') === 0) {
sheetsIconList.push(
`${styles[i].cssRules[j].selectorText.substring(1, styles[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
);
if (/::before/.test(styles[i].cssRules[j].selectorText)) {
sheetsIconList.push(
`${styles[i].cssRules[j].selectorText.substring(1, styles[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
);
}
}
}
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
else reject('未获取到值,请刷新重试');
});
});
};
// 初始化获取 css 样式,这里使用 fontawesome 的图标
const awesomeIconfont = () => {
const getAwesomeIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const styles: any = document.styleSheets;
@ -67,28 +69,38 @@ const awesomeIconfont = () => {
sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 &&
sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
) {
sheetsIconList.push(
`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
);
if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) {
sheetsIconList.push(
`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
);
}
}
}
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
else reject('未获取到值,请刷新重试');
});
});
};
// 定义导出方法集合
/**
* 获取字体图标 `document.styleSheets`
* @method ali 获取阿里字体图标 `<i class="iconfont 图标类名"></i>`
* @method ele 获取 element plus 自带图标 `<i class="图标类名"></i>`
* @method ali 获取 fontawesome 的图标 `<i class="fa 图标类名"></i>`
*/
const initIconfont = {
// iconfont
ali: () => {
return getAlicdnIconfont();
},
// element plus
ele: () => {
return elementPlusIconfont();
return getElementPlusIconfont();
},
// fontawesome
awe: () => {
return awesomeIconfont();
return getAwesomeIconfont();
},
};

View File

@ -0,0 +1,25 @@
import { nextTick } from 'vue';
/**
* 图片懒加载
* @param el dom 目标元素
* @param arr 列表数据
* @description data-xxx 属性用于存储页面或应用程序的私有自定义数据
*/
export const lazyImgLoading = (el: any, arr: any) => {
const io = new IntersectionObserver((res) => {
res.forEach((v: any) => {
if (v.isIntersecting) {
const { img, key } = v.target.dataset;
v.target.src = img;
v.target.onload = () => {
io.unobserve(v.target);
arr[key]['loading'] = false;
};
}
});
});
nextTick(() => {
document.querySelectorAll(el).forEach((img) => io.observe(img));
});
};

View File

@ -1,7 +1,12 @@
import { nextTick } from 'vue';
import loadingCss from '/@/theme/loading.scss';
// 定义方法
/**
* 页面全局 Loading
* @method setCss 载入 css
* @method start 创建 loading
* @method done 移除 loading
*/
export const NextLoading = {
// 载入 css
setCss: () => {
@ -13,7 +18,7 @@ export const NextLoading = {
},
// 创建 loading
start: () => {
const bodys: any = document.body;
const bodys: Element = document.body;
const div = document.createElement('div');
div.setAttribute('class', 'loading-next');
const htmls = `
@ -33,11 +38,13 @@ export const NextLoading = {
`;
div.innerHTML = htmls;
bodys.insertBefore(div, bodys.childNodes[0]);
window.nextLoading = true;
},
// 移除 loading
done: () => {
nextTick(() => {
setTimeout(() => {
window.nextLoading = false;
const el = document.querySelector('.loading-next');
el && el.parentNode?.removeChild(el);
}, 1000);

View File

@ -1,7 +1,6 @@
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { clearSession, getSession } from '/@/utils/storage.ts';
import router, { resetRoute } from '/@/router/index.ts';
import { Session } from '/@/utils/storage';
// 配置新建一个 axios 实例
const service = axios.create({
@ -14,8 +13,8 @@ const service = axios.create({
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (getSession('token')) {
config.headers.common['Authorization'] = `${getSession('token')}`;
if (Session.get('token')) {
config.headers.common['Authorization'] = `${Session.get('token')}`;
}
return config;
},
@ -33,9 +32,8 @@ service.interceptors.response.use(
if (res.code && res.code !== 0) {
// `token` 过期或者账号已在别处登录
if (res.code === 401 || res.code === 4001) {
clearSession(); // 清除浏览器全部临时缓存
router.push('/login'); // 去登录页
resetRoute(); // 删除/重置路由
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
.then(() => {})
.catch(() => {});
@ -59,4 +57,5 @@ service.interceptors.response.use(
}
);
// 导出 axios 实例
export default service;

View File

@ -1,12 +1,12 @@
// 字体图标 url
const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_cl2h21rqdau.css',
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
];
// 第三方 js url
const jsCdnUrlList: Array<string> = [];
// 动态设置字体图标
// 动态批量设置字体图标
export function setCssCdn() {
if (cssCdnUrlList.length <= 0) return false;
cssCdnUrlList.map((v) => {
@ -18,7 +18,7 @@ export function setCssCdn() {
});
}
// 批量设置第三方js
// 动态批量设置第三方js
export function setJsCdn() {
if (jsCdnUrlList.length <= 0) return false;
jsCdnUrlList.map((v) => {
@ -28,11 +28,17 @@ export function setJsCdn() {
});
}
// 设置执行函数
/**
* 批量设置字体图标、动态js
* @method cssCdn 动态批量设置字体图标
* @method jsCdn 动态批量设置第三方js
*/
const setIntroduction = {
// 设置css
cssCdn: () => {
setCssCdn();
},
// 设置js
jsCdn: () => {
setJsCdn();
},

20
src/utils/setWebTitle.ts Normal file
View File

@ -0,0 +1,20 @@
import { nextTick } from 'vue';
import router from '/@/router/index';
import { store } from '/@/store/index';
import { i18n } from '/@/i18n/index';
/**
* 设置浏览器标题国际化
*/
export function useTitle() {
return () => {
nextTick(() => {
let webTitle = '';
let globalTitle: string = store.state.themeConfig.themeConfig.globalTitle;
router.currentRoute.value.path === '/login'
? (webTitle = router.currentRoute.value.meta.title as any)
: (webTitle = i18n.global.t(router.currentRoute.value.meta.title as any));
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
};
}

View File

@ -1,37 +1,53 @@
// 1. localStorage
// 设置永久缓存
export function setLocal(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
}
// 获取永久缓存
export function getLocal(key: string) {
let json: any = window.localStorage.getItem(key);
return JSON.parse(json);
}
// 移除永久缓存
export function removeLocal(key: string) {
window.localStorage.removeItem(key);
}
// 移除全部永久缓存
export function clearLocal() {
window.localStorage.clear();
}
/**
* window.localStorage 浏览器永久缓存
* @method set 设置永久缓存
* @method get 获取永久缓存
* @method remove 移除永久缓存
* @method clear 移除全部永久缓存
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
// 2. sessionStorage
// 设置临时缓存
export function setSession(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
}
// 获取临时缓存
export function getSession(key: string) {
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
}
// 移除临时缓存
export function removeSession(key: string) {
window.sessionStorage.removeItem(key);
}
// 移除全部临时缓存
export function clearSession() {
window.sessionStorage.clear();
}
/**
* window.sessionStorage 浏览器临时缓存
* @method set 设置临时缓存
* @method get 获取临时缓存
* @method remove 移除临时缓存
* @method clear 移除全部临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
},
};

View File

@ -1,6 +1,10 @@
import { ElMessage } from 'element-plus';
// hex颜色转rgb颜色
/**
* hex颜色转rgb颜色
* @param str 颜色值字符串
* @returns 返回处理后的颜色值
*/
export function hexToRgb(str: any) {
let hexs: any = '';
let reg = /^\#?[0-9A-Fa-f]{6}$/;
@ -11,7 +15,13 @@ export function hexToRgb(str: any) {
return hexs;
}
// rgb颜色转Hex颜色
/**
* rgb颜色转Hex颜色
* @param r 代表红色
* @param g 代表绿色
* @param b 代表蓝色
* @returns 返回处理后的颜色值
*/
export function rgbToHex(r: any, g: any, b: any) {
let reg = /^\d{1,3}$/;
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage({ type: 'warning', message: '输入错误的rgb颜色值' });
@ -20,7 +30,12 @@ export function rgbToHex(r: any, g: any, b: any) {
return `#${hexs.join('')}`;
}
// 加深颜色值level为加深的程度限0-1之间
/**
* 加深颜色值
* @param color 颜色值字符串
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getDarkColor(color: any, level: number) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return ElMessage({ type: 'warning', message: '输入错误的hex颜色值' });
@ -29,7 +44,12 @@ export function getDarkColor(color: any, level: number) {
return rgbToHex(rgb[0], rgb[1], rgb[2]);
}
// 变浅颜色值level为加深的程度限0-1之间
/**
* 变浅颜色值
* @param color 颜色值字符串
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getLightColor(color: any, level: number) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return ElMessage({ type: 'warning', message: '输入错误的hex颜色值' });

View File

@ -1,9 +1,14 @@
/**
* 2020.11.29 lyt 整理
* 工具类集合,适用于平时开发
* 新增多行注释信息,鼠标放到方法名即可查看
*/
// 小数或整数(不可以负数)
/**
* 小数或整数(不可以负数)
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberIntegerAndFloat(val: string) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
@ -21,7 +26,11 @@ export function verifyNumberIntegerAndFloat(val: string) {
return v;
}
// 正整数验证
/**
* 正整数验证
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifiyNumberInteger(val: string) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
@ -37,7 +46,11 @@ export function verifiyNumberInteger(val: string) {
return v;
}
// 去掉中文及空格
/**
* 去掉中文及空格
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyCnAndSpace(val: string) {
// 匹配中文与空格
let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
@ -47,7 +60,11 @@ export function verifyCnAndSpace(val: string) {
return v;
}
// 去掉英文及空格
/**
* 去掉英文及空格
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyEnAndSpace(val: string) {
// 匹配英文与空格
let v = val.replace(/[a-zA-Z]+/g, '');
@ -57,7 +74,11 @@ export function verifyEnAndSpace(val: string) {
return v;
}
// 禁止输入空格
/**
* 禁止输入空格
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyAndSpace(val: string) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
@ -65,7 +86,11 @@ export function verifyAndSpace(val: string) {
return v;
}
// 金额用 `,` 区分开
/**
* 金额用 `,` 区分开
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberComma(val: string) {
// 调用小数或整数(不可以负数)方法
let v: any = verifyNumberIntegerAndFloat(val);
@ -79,7 +104,13 @@ export function verifyNumberComma(val: string) {
return v;
}
// 匹配文字变色(搜索时)
/**
* 匹配文字变色(搜索时)
* @param val 当前值字符串
* @param text 要处理的字符串值
* @param color 搜索到时字体高亮颜色
* @returns 返回处理后的字符串
*/
export function verifyTextColor(val: string, text = '', color = 'red') {
// 返回内容,添加颜色
let v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
@ -87,7 +118,12 @@ export function verifyTextColor(val: string, text = '', color = 'red') {
return v;
}
// 数字转中文大写
/**
* 数字转中文大写
* @param val 当前值字符串
* @param unit 默认:仟佰拾亿仟佰拾万仟佰拾元角分
* @returns 返回处理后的字符串
*/
export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾万仟佰拾元角分', v = '') {
// 当前内容字符串添加 2个0为什么??
val += '00';
@ -114,7 +150,11 @@ export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾
return v;
}
// 手机号码
/**
* 手机号码
* @param val 当前值字符串
* @returns 返回 true: 手机号码正确
*/
export function verifyPhone(val: string) {
// false: 手机号码不正确
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(val)) return false;
@ -122,7 +162,11 @@ export function verifyPhone(val: string) {
else return true;
}
// 国内电话号码
/**
* 国内电话号码
* @param val 当前值字符串
* @returns 返回 true: 国内电话号码正确
*/
export function verifyTelPhone(val: string) {
// false: 国内电话号码不正确
if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
@ -130,7 +174,11 @@ export function verifyTelPhone(val: string) {
else return true;
}
// 登录账号 (字母开头允许5-16字节允许字母数字下划线)
/**
* 登录账号 (字母开头允许5-16字节允许字母数字下划线)
* @param val 当前值字符串
* @returns 返回 true: 登录账号正确
*/
export function verifyAccount(val: string) {
// false: 登录账号不正确
if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
@ -138,7 +186,11 @@ export function verifyAccount(val: string) {
else return true;
}
// 密码 (以字母开头长度在6~16之间只能包含字母、数字和下划线)
/**
* 密码 (以字母开头长度在6~16之间只能包含字母、数字和下划线)
* @param val 当前值字符串
* @returns 返回 true: 密码正确
*/
export function verifyPassword(val: string) {
// false: 密码不正确
if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
@ -146,7 +198,11 @@ export function verifyPassword(val: string) {
else return true;
}
// 强密码 (字母+数字+特殊字符长度在6-16之间)
/**
* 强密码 (字母+数字+特殊字符长度在6-16之间)
* @param val 当前值字符串
* @returns 返回 true: 强密码正确
*/
export function verifyPasswordPowerful(val: string) {
// false: 强密码不正确
if (!/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
@ -155,7 +211,14 @@ export function verifyPasswordPowerful(val: string) {
else return true;
}
// 密码强度
/**
* 密码强度
* @param val 当前值字符串
* @description 弱:纯数字,纯字母,纯特殊字符
* @description 中:字母+数字,字母+特殊字符,数字+特殊字符
* @description 强:字母+数字+特殊字符
* @returns 返回处理后的字符串:弱、中、强
*/
export function verifyPasswordStrength(val: string) {
let v = '';
// 弱:纯数字,纯字母,纯特殊字符
@ -169,7 +232,11 @@ export function verifyPasswordStrength(val: string) {
return v;
}
// IP地址
/**
* IP地址
* @param val 当前值字符串
* @returns 返回 true: IP地址正确
*/
export function verifyIPAddress(val: string) {
// false: IP地址不正确
if (
@ -182,7 +249,11 @@ export function verifyIPAddress(val: string) {
else return true;
}
// 邮箱
/**
* 邮箱
* @param val 当前值字符串
* @returns 返回 true: 邮箱正确
*/
export function verifyEmail(val: string) {
// false: 邮箱不正确
if (
@ -195,7 +266,11 @@ export function verifyEmail(val: string) {
else return true;
}
// 身份证
/**
* 身份证
* @param val 当前值字符串
* @returns 返回 true: 身份证正确
*/
export function verifyIdCard(val: string) {
// false: 身份证不正确
if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val)) return false;
@ -203,7 +278,11 @@ export function verifyIdCard(val: string) {
else return true;
}
// 姓名
/**
* 姓名
* @param val 当前值字符串
* @returns 返回 true: 姓名正确
*/
export function verifyFullName(val: string) {
// false: 姓名不正确
if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val)) return false;
@ -211,7 +290,11 @@ export function verifyFullName(val: string) {
else return true;
}
// 邮政编码
/**
* 邮政编码
* @param val 当前值字符串
* @returns 返回 true: 邮政编码正确
*/
export function verifyPostalCode(val: string) {
// false: 邮政编码不正确
if (!/^[1-9][0-9]{5}$/.test(val)) return false;
@ -219,7 +302,11 @@ export function verifyPostalCode(val: string) {
else return true;
}
// url
/**
* url 处理
* @param val 当前值字符串
* @returns 返回 true: url 正确
*/
export function verifyUrl(val: string) {
// false: url不正确
if (
@ -232,7 +319,11 @@ export function verifyUrl(val: string) {
else return true;
}
// 车牌号
/**
* 车牌号
* @param val 当前值字符串
* @returns 返回 true车牌号正确
*/
export function verifyCarNum(val: string) {
// false: 车牌号不正确
if (

View File

@ -1,20 +1,25 @@
// vite 打包相关
import dotenv from 'dotenv';
// 定义接口类型声明
export interface ViteEnv {
VITE_PORT: number;
VITE_OPEN: boolean;
VITE_PUBLIC_PATH: string;
}
/**
* vite 打包相关
* @link 参考https://cn.vitejs.dev/guide/env-and-mode.html
* @returns 返回 `VITE_xxx` 环境变量和模式信息
*/
export function loadEnv(): ViteEnv {
const env = process.env.NODE_ENV;
const ret: any = {};
const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env', ,];
const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env'];
envList.forEach((e) => {
dotenv.config({ path: e });
});
for (const envName of Object.keys(process.env)) {
console.log(envName);
let realName = (process.env as any)[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') realName = Number(realName);

View File

@ -1,5 +1,5 @@
// 页面添加水印效果
const setWatermark = (str: any) => {
const setWatermark = (str: string) => {
const id = '1.23452384164.123412416';
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
const can = document.createElement('canvas');
@ -19,16 +19,21 @@ const setWatermark = (str: any) => {
div.style.left = '0px';
div.style.position = 'fixed';
div.style.zIndex = '10000000';
div.style.width = document.documentElement.clientWidth + 'px';
div.style.height = document.documentElement.clientHeight + 'px';
div.style.width = `${document.documentElement.clientWidth}px`;
div.style.height = `${document.documentElement.clientHeight}px`;
div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`;
document.body.appendChild(div);
return id;
};
/**
* 页面添加水印效果
* @method set 设置水印
* @method del 删除水印
*/
const watermark = {
// 设置水印
set: (str: any) => {
set: (str: string) => {
let id = setWatermark(str);
if (document.getElementById(id) === null) id = setWatermark(str);
},
@ -39,4 +44,5 @@ const watermark = {
},
};
// 导出方法
export default watermark;

View File

@ -1,4 +1,7 @@
// sky 天气
/**
* sky 天气
* @returns 返回模拟数据
*/
export const skyList = [
{
v1: '时间',
@ -24,7 +27,10 @@ export const skyList = [
},
];
// 当前设置状态
/**
* 当前设置状态
* @returns 返回模拟数据
*/
export const dBtnList = [
{
v2: '阳光玫瑰种植',
@ -33,7 +39,10 @@ export const dBtnList = [
},
];
// 当前设备监测
/**
* 当前设备监测
* @returns 返回模拟数据
*/
export const chartData4List = [
{
label: '温度',
@ -49,7 +58,10 @@ export const chartData4List = [
},
];
// 3DEarth 地图周围按钮组
/**
* 3DEarth 地图周围按钮组
* @returns 返回模拟数据
*/
export const earth3DBtnList = [
{
topLevelClass: 'fixed-top',

View File

@ -12,7 +12,7 @@
<script lang="ts">
import { reactive, toRefs, onBeforeMount, onUnmounted } from 'vue';
import { formatDate } from '/@/utils/formatTime.ts';
import { formatDate } from '/@/utils/formatTime';
export default {
name: 'chartHead',
setup() {

View File

@ -1,6 +1,6 @@
<template>
<div class="chart-scrollbar" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="chart-warp layout-view-bg-white">
<div class="chart-scrollbar layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="chart-warp">
<div class="chart-warp-top">
<ChartHead />
</div>
@ -202,12 +202,12 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index.ts';
import { toRefs, reactive, computed, onMounted, getCurrentInstance, watch, nextTick, onActivated } from 'vue';
import { useStore } from '/@/store/index';
import ChartHead from '/@/views/chart/head.vue';
import * as echarts from 'echarts';
import 'echarts-wordcloud';
import { skyList, dBtnList, chartData4List, earth3DBtnList } from '/@/views/chart/chart.ts';
import { skyList, dBtnList, chartData4List, earth3DBtnList } from '/@/views/chart/chart';
export default {
name: 'chartIndex',
components: { ChartHead },
@ -220,12 +220,18 @@ export default {
dBtnList,
chartData4List,
earth3DBtnList,
myCharts: [],
});
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return `114px`;
else return `80px`;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
// 初始化中间图表1
const initChartsCenterOne = () => {
@ -286,9 +292,7 @@ export default {
],
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
state.myCharts.push(myChart);
};
// 初始化近7天产品追溯扫码统计
const initChartsSevenDays = () => {
@ -333,9 +337,7 @@ export default {
],
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
state.myCharts.push(myChart);
};
// 初始化近30天预警总数
const initChartsWarning = () => {
@ -370,9 +372,7 @@ export default {
],
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
state.myCharts.push(myChart);
};
// 初始化当前设备监测
const initChartsMonitor = () => {
@ -412,9 +412,7 @@ export default {
],
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
state.myCharts.push(myChart);
};
// 初始化近7天投入品记录
const initChartsInvestment = () => {
@ -444,10 +442,20 @@ export default {
],
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
state.myCharts.push(myChart);
};
// 批量设置 echarts resize
const initEchartsResizeFun = () => {
nextTick(() => {
for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
});
};
// 批量设置 echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', initEchartsResizeFun);
};
// 页面加载时
onMounted(() => {
initChartsCenterOne();
@ -455,7 +463,19 @@ export default {
initChartsWarning();
initChartsMonitor();
initChartsInvestment();
initEchartsResize();
});
// 由于页面缓存原因keep-alive
onActivated(() => {
initEchartsResizeFun();
});
// 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
watch(
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
() => {
initEchartsResizeFun();
}
);
return {
initTagViewHeight,
...toRefs(state),

View File

@ -20,13 +20,13 @@
<script lang="ts">
import { useRouter } from 'vue-router';
import { clearSession } from '/@/utils/storage.ts';
import { Session } from '/@/utils/storage';
export default {
name: '401',
setup() {
const router = useRouter();
const onSetAuth = () => {
clearSession();
Session.clear();
router.push('/login');
};
return {

View File

@ -2,14 +2,14 @@
<div id="printRref">
<el-card shadow="hover" header="复制剪切演示">
<el-alert
title="感谢优秀的 `clipboard`项目地址https://github.com/zenorocha/clipboard.js`"
title="感谢优秀的 `vue-clipboard3`项目地址https://github.com/JamieCurnow/vue-clipboard3`"
type="success"
:closable="false"
class="mb15"
></el-alert>
<el-input placeholder="请输入内容" v-model="copyVal">
<template #append>
<el-button @click="onCopyClick($event.target)" ref="copyBtnRef">复制链接</el-button>
<el-button @click="copyText(copyVal)">复制链接</el-button>
</template>
</el-input>
<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
@ -18,43 +18,20 @@
</template>
<script lang="ts">
import { reactive, toRefs, ref, onMounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import ClipboardJS from 'clipboard';
import { useI18n } from 'vue-i18n';
import { reactive, toRefs, onMounted } from 'vue';
import commonFunction from '/@/utils/commonFunction';
export default {
name: 'funClipboard',
setup() {
const { t } = useI18n();
const copyBtnRef = ref();
const { copyText } = commonFunction();
const state = reactive({
copyVal: 'https://gitee.com/lyt-top/vue-next-admin',
shearVal: '',
});
// 复制链接点击
const onCopyClick = (target: any) => {
const clipboard = new ClipboardJS(target, {
text: () => state.copyVal,
});
clipboard.on('success', () => {
ElMessage.success(t('message.layout.copyTextSuccess'));
clipboard.destroy();
});
clipboard.on('error', () => {
ElMessage.error(t('message.layout.copyTextError'));
clipboard.destroy();
});
};
// 页面加载时
onMounted(() => {
nextTick(() => {
// 初始化复制功能,防止点击两次才可以复制
onCopyClick(copyBtnRef.value.$el);
});
});
onMounted(() => {});
return {
copyBtnRef,
onCopyClick,
copyText,
...toRefs(state),
};
},

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