mirror of
https://gitee.com/lyt-top/vue-next-admin
synced 2026-06-19 06:45:57 +08:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e57dde4bab | |||
| fdf9cd4abe | |||
| 664e70de6c | |||
| 4259e9931f | |||
| 3a57a7f4ff | |||
| fab396b503 | |||
| 41f6992630 | |||
| a402bd3c3a | |||
| 7d004ee948 | |||
| 5a75f2626e | |||
| 21248a361e | |||
| 6441700ae1 | |||
| f716918ef2 | |||
| abf9f1ae08 | |||
| 1cd056cb83 | |||
| 2c8dbace6c | |||
| 77b7621e87 | |||
| d6fceb257c | |||
| 23a0c21f15 | |||
| 6eaad912f5 | |||
| faf372a8b9 | |||
| d375051ec3 | |||
| c630f04194 | |||
| f6302a8b40 | |||
| 9991d931a2 | |||
| 35fdb164e9 | |||
| 695884a1aa | |||
| 65c953a613 | |||
| 4f36e46f7b | |||
| ba4247febd | |||
| c942aec7f1 | |||
| c180c24769 | |||
| ec1e963358 | |||
| 805f991791 | |||
| d50627e0df | |||
| 638fe523cb | |||
| 6dcc2c78c1 | |||
| 8c216f6e94 | |||
| 49c5eaf1bc |
20
.eslintrc.js
20
.eslintrc.js
@ -16,16 +16,16 @@ module.exports = {
|
||||
rules: {
|
||||
// http://eslint.cn/docs/rules/
|
||||
// https://eslint.vuejs.org/rules/
|
||||
'@type-eslint/ban-ts-ignore': 'off',
|
||||
'@type-eslint/explicit-function-return-type': 'off',
|
||||
'@type-eslint/no-explicit-any': 'off',
|
||||
'@type-eslint/no-var-requires': 'off',
|
||||
'@type-eslint/no-empty-function': 'off',
|
||||
'@type-eslint/no-use-before-define': 'off',
|
||||
'@type-eslint/ban-ts-comment': 'off',
|
||||
'@type-eslint/ban-types': 'off',
|
||||
'@type-eslint/no-non-null-assertion': 'off',
|
||||
'@type-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
|
||||
81
CHANGELOG.md
81
CHANGELOG.md
@ -2,6 +2,87 @@
|
||||
|
||||
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
|
||||
|
||||
## 2.2.0
|
||||
|
||||
`2022.07.10`
|
||||
|
||||
⚡⚡⚡ [/sec/stores/userInfo.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/stores/userInfo.ts) 下添加了 `getApiUserInfo` 接口模拟数据 `setTimeout` 为 3 秒
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🐞 修复 [主界面重新授权按钮点击卡死不跳转登录界面#I5C3JS](https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS),感谢[@Hero-Typ](https://gitee.com/tian_yu_peng)
|
||||
- 🐞 修复 编译警告[#I5CVSB](https://gitee.com/lyt-top/vue-next-admin/issues/I5CVSB),全局替换成 `:deep(attr)`,感谢[@Linvas](https://gitee.com/linvas)。参考文档:[vue3 sfc-style](https://v3.cn.vuejs.org/api/sfc-style.html#style-scoped)。`node_modules\print-js\dist\print.js` 需 `print-js` 作者适配或去除 `package.json` 中的 `"print-js": "^1.6.0"`
|
||||
- 🐞 修复 [vue-next-admin-template-js 版本前端控制路由:userInfo.js 请求用户信息接口报错,加载不到路由 可以写个定时器模拟一下接口 一样的报错#I5F1HP](https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP),感谢[@白开水](https://gitee.com/libin951223)
|
||||
|
||||
## 2.1.1
|
||||
|
||||
`2022.05.27`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 深色模式下,`<el-button text></el-button>` 时,`:active` 样式
|
||||
- 🎯 优化 [页面缓存在刷新之后失效 #I58U75](https://gitee.com/lyt-top/vue-next-admin/issues/I58U75)),感谢[@ls0428](https://gitee.com/ls0428)
|
||||
- 🎯 优化 [SvgIcon 对下载的 Svg 图像设置颜色无效 #I59ND0](https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0)),感谢[@elus_z](https://gitee.com/elus_z)
|
||||
- 🎯 优化 `/src/utils/toolsValidate.ts` 工具类
|
||||
- 🐞 修复 [布局切换,TagsView 显示的 tab 会多一个出来 #I58WGM](https://gitee.com/lyt-top/vue-next-admin/issues/I58WGM),感谢[@lg_boy](https://gitee.com/lg_boy)
|
||||
- 🐞 修复 [如果设置顶部面包屑导航开启图标 isBreadcrumbIcon=true 后,样式有点问题 如果不开启就是正常的 #I58VB8](https://gitee.com/lyt-top/vue-next-admin/issues/I58VB8)
|
||||
- 🐞 修复 地址栏路由地址输入错误时,返回首页后,再次输入路由地址错误时,不跳转 404 问题
|
||||
- 🐞 修复 [2.1.0 版本的图标选择组件多次点击后功能失效 #I590TH](https://gitee.com/lyt-top/vue-next-admin/issues/I590TH),感谢[@quber](https://gitee.com/quber)
|
||||
|
||||
## 2.1.0
|
||||
|
||||
`2022.04.18`
|
||||
|
||||
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。因为 `vuex` 替换成 `pinia`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 部分界面图片不显示问题(更换 gitee 在线图片地址源)
|
||||
- 🎯 优化 各界面方法引入与逻辑之间添加一行空行,方便区分内容
|
||||
- 🎯 优化 图标选择器 [#I4YAHB](https://gitee.com/lyt-top/vue-next-admin/issues/I4YAHB),感谢[@真有你的](https://gitee.com/sunliusen)
|
||||
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
|
||||
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
|
||||
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
|
||||
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue`
|
||||
- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题
|
||||
- 🎯 优化 [如果 url 中有中文等特殊字符,第一次切换该 tab 时 keep-alive 失效#I55JS7](https://gitee.com/lyt-top/vue-next-admin/issues/I55JS7),感谢[yuyong1566](https://gitee.com/yuyong1566)
|
||||
- 🎯 优化 [wangEditor](https://www.wangeditor.com/) 更新到 v5,[vue3 版本线上示例中 wangeditor 富文本编辑器 demo 实例,无法换行#I5565B](https://gitee.com/lyt-top/vue-next-admin/issues/I5565B),感谢@[jenchih](https://gitee.com/jenchih)
|
||||
- 🎯 优化 [在关闭 tagview 时,高度刷新时会会变化,出现滚动条](https://gitee.com/lyt-top/vue-next-admin/issues/I55FHM),感谢[张松](https://gitee.com/zs310071113)
|
||||
- 🎯 优化 [路由参数](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)演示
|
||||
- 🎉 新增 [vuex](https://vuex.vuejs.org/) 替换成 [pinia](https://pinia.vuejs.org/getting-started.html)
|
||||
- 🎉 新增 tagsView 支持自定义 tagsView 名称(文章详情时有用),前往体验:[路由参数/普通路由](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)。新增 tagsView 支持自定义名称国际化,感谢[@q7but](https://gitee.com/q7but)、[!22 add 添加自定义 tagVIewName 拓展,支持国际化](https://gitee.com/lyt-top/vue-next-admin/pulls/22/files)、感谢[@tony_tong_xin](https://gitee.com/tony_tong_xin)
|
||||
- 🐞 修复 适配 `"element-plus": "^2.1.9",2.2.0` 版本
|
||||
- 🐞 修复 [导航栏横向布局后,一级菜单显示问题#I4Z3M3](https://gitee.com/lyt-top/vue-next-admin/issues/I4Z3M3)
|
||||
- 🐞 修复 横向布局三级及以上导航菜单高亮、导航高度不统一问题
|
||||
- 🐞 修复 分栏模式下,选中的菜单是 primary 样式,鼠标移入字也变成 primary 色了,感谢群友@孤夜-流殇
|
||||
- 🐞 修复 [vuex 里面改了颜色 但是不生效 #I4WFMA](https://gitee.com/lyt-top/vue-next-admin/issues/I4WFMA)
|
||||
- 🐞 修复 全局主题 primary 清空颜色后报错,[#I4X0LG](https://gitee.com/lyt-top/vue-next-admin/issues/I4X0LG),感谢[面向 BUG 编程](https://gitee.com/fhtfy)
|
||||
- 🐞 修复 [.eslintrc.js 文件 rules 标签名错误 #I53IPK](https://gitee.com/lyt-top/vue-next-admin/issues/I53IPK),感谢[yuyong1566](https://gitee.com/yuyong1566)
|
||||
- 🐞 修复 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题
|
||||
- 🐞 修复 `router.push` 路径找不到时报错问题,`404、401 界面` 已移入到 `main` 主布局里(之前全屏)
|
||||
- 🐞 修复 [全局修改组件大小失效了](https://gitee.com/lyt-top/vue-next-admin/issues/I551RP),感谢[lg_boy](https://gitee.com/lg_boy)
|
||||
- 🐞 修复 [修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效,问题解决#I567R1](https://gitee.com/lyt-top/vue-next-admin/issues/I567R1),感谢[@lanbao123](https://gitee.com/lanbao123)
|
||||
- 🐞 修复 [标记为需要缓存的 tab 页后,再次从左侧菜单打开,还是显示被缓存的页面内容#I4UY3G](https://gitee.com/lyt-top/vue-next-admin/issues/I4UY3G),感谢@axcc1234、特别感谢群友@华仔
|
||||
- 🌈 重构 路由(`/src/router/index.ts`)解决 No match found for location with path "xxx"(前端控制,后端控制未解决) 问题
|
||||
|
||||
## 2.0.2
|
||||
|
||||
`2022.03.04`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 Alert 提示添加边框
|
||||
- 🎯 优化 功能 / 数字滚动 演示界面
|
||||
- 🐞 修复 全局主题按钮颜色 :active 问题
|
||||
- 🐞 修复 Dropdown 下拉菜单样式问题
|
||||
- 🐞 修复 SvgIcon 图标组件动态切换时报警告问题,[SvgIcon 改变 name 时可能导致图像不显示](https://gitee.com/lyt-top/vue-next-admin/issues/I4VGE0),感谢@axcc1234
|
||||
|
||||
## 2.0.1
|
||||
|
||||
`2022.02.25`
|
||||
|
||||
- 🌟 更新 依赖更新最新版本
|
||||
- 🎯 优化 svgIcon 图标组件
|
||||
- 🎯 优化 vite.config.ts 打包,感谢群友@YourObjec
|
||||
- 🐞 修复 tagViews 开启图标不显示问题(风格 5),感谢群友@坏人
|
||||
- 🐞 修复 [Element Plus 1.2.0-beta.6 以后的版本 el-table 在移动端无法左右滑动](https://gitee.com/lyt-top/vue-next-admin/issues/I4UPTP),感谢@YGDada
|
||||
|
||||
## 2.0.0
|
||||
|
||||
`2022.02.21`
|
||||
|
||||
34
README.md
34
README.md
@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/logo/logo-text.svg">
|
||||
<img src="https://img-blog.csdnimg.cn/9efd5420327a46b7bd6d93524a97229d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_14,color_FFFFFF,t_70,g_se,x_16">
|
||||
<p align="center">
|
||||
<a href="https://v3.vuejs.org/" target="_blank">
|
||||
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
|
||||
@ -41,9 +41,9 @@
|
||||
|
||||
#### 🏭 环境支持
|
||||
|
||||
| Edge | last 2 versions | last 2 versions | last 2 versions |
|
||||
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
||||
|  |  |  |  |
|
||||
| Edge | Firefox | Chrome | Safari |
|
||||
| --------- | ------------ | ----------- | ----------- |
|
||||
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
|
||||
|
||||
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||
|
||||
@ -70,27 +70,38 @@ cnpm run build
|
||||
|
||||
#### 💯 学习交流加 QQ 群
|
||||
|
||||
- 若加群了没同意(一般秒过),那就是群满了(500 人群),请换一个群试试,3 群未满
|
||||
- 若加群了没同意(一般秒过),那就是群满了(500 人群),请换一个群试试。群会定期清理半年(6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!
|
||||
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
|
||||
- 群号码:
|
||||
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
|
||||
2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>
|
||||
3 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=02EWb5P2JkP-8iwzaDadgFdxA0HSHPpn&jump_from=webapi">795345435</a>
|
||||
4 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=0gTFO04WwkeZZ6R4lju6gucbeXHK-wNd&jump_from=webapi">736626228</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/qq1.png" width="220" height="220" alt="vue-next-admin 讨论群1" title="vue-next-admin 讨论群1"/>
|
||||
<img src="https://img-blog.csdnimg.cn/35e00f12a3fe4820892ec630ca72f15f.png" width="220" height="220" alt="vue-next-admin 讨论群1" 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 讨论群2" title="vue-next-admin 讨论群2"/>
|
||||
<img src="https://img-blog.csdnimg.cn/5f1b548abd9f434eb41edde31d1c1fa9.png" width="220" height="220" alt="vue-next-admin 讨论群2" title="vue-next-admin 讨论群2"/>
|
||||
</a>
|
||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=02EWb5P2JkP-8iwzaDadgFdxA0HSHPpn&jump_from=webapi">
|
||||
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq3.png" width="220" height="220" alt="vue-next-admin 讨论群3" title="vue-next-admin 讨论群3"/>
|
||||
<img src="https://img-blog.csdnimg.cn/70c8a012dd304246bddeac2184c4ab3a.png" width="220" height="220" alt="vue-next-admin 讨论群3" title="vue-next-admin 讨论群3"/>
|
||||
</a>
|
||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=0gTFO04WwkeZZ6R4lju6gucbeXHK-wNd&jump_from=webapi">
|
||||
<img src="https://img-blog.csdnimg.cn/e5c9704eed1342bc9d9e74b37203402d.png" width="220" height="220" alt="vue-next-admin 讨论群4" title="vue-next-admin 讨论群4"/>
|
||||
</a>
|
||||
|
||||
#### 💒 集成后端
|
||||
|
||||
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
|
||||
- <a target="_blank" href="https://www.gnet.top/public">@甜蜜蜜 GoPro 平台</a>
|
||||
- <a target="_blank" href="https://toscode.gitee.com/GionConnection/gopro_free">@甜蜜蜜 GoPro 平台</a>
|
||||
- <a target="_blank" href="https://gitee.com/GionConnection/niupi-free">@甜蜜蜜 NiuPi 平台</a>
|
||||
- <a target="_blank" href="https://gitee.com/tiger1103/gfast/tree/os-v3/">@游子 GFast-V3</a>
|
||||
- <a target="_blank" href="https://gitee.com/diygw/diygw-ui-php/">@diygw.com gw-ui-php</a>
|
||||
- <a target="_blank" href="https://gitee.com/zsvg/vboot-net">@zsvg vboot-net</a>
|
||||
- <a target="_blank" href="https://gitee.com/zsvg/vboot-java">@zsvg vboot-java</a>
|
||||
- <a target="_blank" href="https://gitee.com/wonderful-code/buildadmin">@青红造了个白 buildadmin</a>
|
||||
- <a target="_blank" href="https://github.com/xiaodingding/iotfast">@Goodwell iotfast(一个开源的物联网平台)</a>
|
||||
|
||||
#### ❤️ 鸣谢列表
|
||||
|
||||
@ -115,10 +126,8 @@ cnpm run build
|
||||
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
|
||||
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs</a>
|
||||
- <a href="https://github.com/crabbly/Print.js" target="_blank">print-js</a>
|
||||
- <a href="https://github.com/likaia/screen-shot" target="_blank">vue-web-screen-shot</a>
|
||||
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</a>
|
||||
- <a href="https://github.com/antoniandre/splitpanes" target="_blank">splitpanes</a>
|
||||
- <a href="https://github.com/yimijianfang/vue-drag-verify" target="_blank">vue-drag-verify</a>
|
||||
- <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a>
|
||||
|
||||
#### 💕 特别感谢
|
||||
@ -126,8 +135,9 @@ cnpm run build
|
||||
特别感谢老哥们的建议、指导与帮忙。谢谢!
|
||||
|
||||
- <a href="https://gitee.com/click33/sa-plus" target="_blank">@省长</a>
|
||||
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参
|
||||
- <a href="https://gitee.com/jskz/Jskz-SpringCloud" target="_blank">@唐参</a>
|
||||
- <a href="https://gitee.com/chuange" target="_blank">@川歌</a>
|
||||
- @华仔
|
||||
|
||||
#### 💌 支持作者
|
||||
|
||||
|
||||
6925
package-lock.json
generated
6925
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
63
package.json
63
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-next-admin",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.0",
|
||||
"description": "vue3 vite next admin template",
|
||||
"author": "lyt_20201208",
|
||||
"license": "MIT",
|
||||
@ -10,47 +10,48 @@
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^0.2.7",
|
||||
"axios": "^0.26.0",
|
||||
"countup.js": "^2.0.8",
|
||||
"@element-plus/icons-vue": "^2.0.6",
|
||||
"@wangeditor/editor": "^5.1.1",
|
||||
"axios": "^0.27.2",
|
||||
"countup.js": "^2.3.2",
|
||||
"cropperjs": "^1.5.12",
|
||||
"echarts": "^5.3.0",
|
||||
"echarts-gl": "^2.0.8",
|
||||
"echarts": "^5.3.3",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"echarts-wordcloud": "^2.0.0",
|
||||
"element-plus": "^2.0.2",
|
||||
"element-plus": "^2.2.9",
|
||||
"js-cookie": "^3.0.1",
|
||||
"jsplumb": "^2.15.6",
|
||||
"mitt": "^3.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.14",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcodejs2-fixes": "^0.0.2",
|
||||
"screenfull": "^6.0.1",
|
||||
"sortablejs": "^1.14.0",
|
||||
"splitpanes": "^3.0.6",
|
||||
"vue": "^3.2.31",
|
||||
"vue-clipboard3": "^1.0.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"splitpanes": "^3.1.1",
|
||||
"vue": "^3.2.37",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-grid-layout": "^3.0.0-beta1",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-router": "^4.0.12",
|
||||
"vuex": "^4.0.2",
|
||||
"wangeditor": "^4.7.11"
|
||||
"vue-i18n": "^9.1.10",
|
||||
"vue-router": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.19",
|
||||
"@types/node": "^18.0.3",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||
"@typescript-eslint/parser": "^5.12.0",
|
||||
"@vitejs/plugin-vue": "^2.2.2",
|
||||
"@vue/compiler-sfc": "^3.2.31",
|
||||
"dotenv": "^16.0.0",
|
||||
"eslint": "^8.9.0",
|
||||
"eslint-plugin-vue": "^8.4.1",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.49.8",
|
||||
"sass-loader": "^12.6.0",
|
||||
"typescript": "^4.5.5",
|
||||
"vite": "^2.8.4",
|
||||
"vue-eslint-parser": "^8.2.0"
|
||||
"@types/sortablejs": "^1.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||
"@typescript-eslint/parser": "^5.30.5",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"dotenv": "^16.0.1",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^2.9.14",
|
||||
"vue-eslint-parser": "^9.0.3"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
1
plugins.d.ts
vendored
1
plugins.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
declare module 'vue-grid-layout';
|
||||
declare module 'qrcodejs2-fixes';
|
||||
declare module 'splitpanes';
|
||||
declare module 'js-cookie';
|
||||
|
||||
32
src/App.vue
32
src/App.vue
@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
|
||||
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
|
||||
<LockScreen v-if="getThemeConfig.isLockScreen" />
|
||||
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
|
||||
<CloseFull />
|
||||
<router-view v-show="themeConfig.lockScreenTime > 1" />
|
||||
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
|
||||
<CloseFull v-if="!themeConfig.isLockScreen" />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import { Local, Session } from '/@/utils/storage';
|
||||
import setIntroduction from '/@/utils/setIconfont';
|
||||
import LockScreen from '/@/layout/lockScreen/index.vue';
|
||||
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
|
||||
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'app',
|
||||
components: { LockScreen, Setings, CloseFull },
|
||||
@ -24,17 +27,15 @@ export default defineComponent({
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const setingsRef = ref();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const state = reactive({
|
||||
i18nLocale: null,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
// 获取全局组件大小
|
||||
const getGlobalComponentSize = computed(() => {
|
||||
return other.globalComponentSize;
|
||||
return other.globalComponentSize();
|
||||
});
|
||||
// 布局配置弹窗打开
|
||||
const openSetingsDrawer = () => {
|
||||
@ -60,12 +61,12 @@ export default defineComponent({
|
||||
});
|
||||
// 获取缓存中的布局配置
|
||||
if (Local.get('themeConfig')) {
|
||||
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
|
||||
storesThemeConfig.setThemeConfig(Local.get('themeConfig'));
|
||||
document.documentElement.style.cssText = Local.get('themeConfigStyle');
|
||||
}
|
||||
// 获取缓存中的全屏配置
|
||||
if (Session.get('isTagsViewCurrenFull')) {
|
||||
store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
|
||||
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -79,11 +80,14 @@ export default defineComponent({
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
themeConfig,
|
||||
setingsRef,
|
||||
getThemeConfig,
|
||||
getGlobalComponentSize,
|
||||
...toRefs(state),
|
||||
};
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'auth',
|
||||
props: {
|
||||
@ -14,10 +16,11 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === props.value);
|
||||
return userInfos.value.authBtnList.some((v: string) => v === props.value);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'authAll',
|
||||
props: {
|
||||
@ -15,10 +17,11 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
// 获取 vuex 中的用户权限
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 pinia 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
|
||||
return judementSameArr(props.value, userInfos.value.authBtnList);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'auths',
|
||||
props: {
|
||||
@ -14,11 +16,12 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const stores = useUserInfo();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
let flag = false;
|
||||
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
|
||||
userInfos.value.authBtnList.map((val: string) => {
|
||||
props.value.map((v) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
import { reactive, toRefs, nextTick, defineComponent } from 'vue';
|
||||
import Cropper from 'cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'cropperIndex',
|
||||
setup() {
|
||||
@ -101,7 +102,7 @@ export default defineComponent({
|
||||
display: inline-block;
|
||||
height: 350px;
|
||||
flex: 1;
|
||||
border: var(--el-border-base);
|
||||
border: 1px solid var(--el-border-color);
|
||||
background: var(--el-color-white);
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
<template>
|
||||
<div class="editor-container">
|
||||
<div :id="id"></div>
|
||||
<div ref="editorToolbar"></div>
|
||||
<div ref="editorContent" :style="{ height }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, watch, defineComponent } from 'vue';
|
||||
import wangeditor from 'wangeditor';
|
||||
import { createEditor, createToolbar, IEditorConfig, IToolbarConfig, IDomEditor } from '@wangeditor/editor';
|
||||
import '@wangeditor/editor/dist/css/style.css';
|
||||
import { toolbarKeys } from './toolbar';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface WangeditorState {
|
||||
editorToolbar: HTMLDivElement | null;
|
||||
editorContent: HTMLDivElement | null;
|
||||
editor: any;
|
||||
}
|
||||
|
||||
@ -31,33 +36,63 @@ export default defineComponent({
|
||||
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,
|
||||
// https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
|
||||
// 模式,可选 <default|simple>,默认 default
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => 'default',
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: () => '310px',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const state = reactive<WangeditorState>({
|
||||
editorToolbar: null,
|
||||
editor: null,
|
||||
editorContent: null,
|
||||
});
|
||||
// 初始化富文本
|
||||
// https://doc.wangeditor.com/
|
||||
const initWangeditor = () => {
|
||||
state.editor = new wangeditor(`#${props.id}`);
|
||||
state.editor.config.zIndex = 1;
|
||||
state.editor.config.placeholder = props.placeholder;
|
||||
state.editor.config.uploadImgShowBase64 = true;
|
||||
state.editor.config.showLinkImg = false;
|
||||
onWangeditorChange();
|
||||
state.editor.create();
|
||||
state.editor.txt.html(props.modelValue);
|
||||
props.isDisable ? state.editor.disable() : state.editor.enable();
|
||||
};
|
||||
// 内容改变时
|
||||
const onWangeditorChange = () => {
|
||||
state.editor.config.onchange = (html: string) => {
|
||||
emit('update:modelValue', html);
|
||||
// 富文本配置
|
||||
const wangeditorConfig = () => {
|
||||
const editorConfig: Partial<IEditorConfig> = { MENU_CONF: {} };
|
||||
props.isDisable ? (editorConfig.readOnly = true) : (editorConfig.readOnly = false);
|
||||
editorConfig.placeholder = props.placeholder;
|
||||
editorConfig.onChange = (editor: IDomEditor) => {
|
||||
// console.log('content', editor.children);
|
||||
// console.log('html', editor.getHtml());
|
||||
emit('update:modelValue', editor.getHtml());
|
||||
};
|
||||
(<any>editorConfig).MENU_CONF['uploadImage'] = {
|
||||
base64LimitSize: 10 * 1024 * 1024,
|
||||
};
|
||||
return editorConfig;
|
||||
};
|
||||
//
|
||||
const toolbarConfig = () => {
|
||||
const toolbarConfig: Partial<IToolbarConfig> = {};
|
||||
toolbarConfig.toolbarKeys = toolbarKeys;
|
||||
return toolbarConfig;
|
||||
};
|
||||
// 初始化富文本
|
||||
// https://www.wangeditor.com/
|
||||
const initWangeditor = () => {
|
||||
state.editor = createEditor({
|
||||
html: props.modelValue,
|
||||
selector: state.editorContent!,
|
||||
config: wangeditorConfig(),
|
||||
mode: props.mode,
|
||||
});
|
||||
createToolbar({
|
||||
editor: state.editor,
|
||||
selector: state.editorToolbar!,
|
||||
mode: props.mode,
|
||||
config: toolbarConfig(),
|
||||
});
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@ -68,7 +103,8 @@ export default defineComponent({
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
state.editor.txt.html(value);
|
||||
state.editor.clear();
|
||||
state.editor.dangerouslyInsertHtml(value);
|
||||
}
|
||||
);
|
||||
return {
|
||||
|
||||
60
src/components/editor/toolbar.ts
Normal file
60
src/components/editor/toolbar.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 工具栏配置
|
||||
*/
|
||||
export const toolbarKeys = [
|
||||
'headerSelect',
|
||||
'blockquote',
|
||||
'|',
|
||||
'bold',
|
||||
'underline',
|
||||
'italic',
|
||||
{
|
||||
key: 'group-more-style',
|
||||
title: '更多',
|
||||
iconSvg:
|
||||
'<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',
|
||||
menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'],
|
||||
},
|
||||
'color',
|
||||
'bgColor',
|
||||
'|',
|
||||
'fontSize',
|
||||
'fontFamily',
|
||||
'lineHeight',
|
||||
'|',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'todo',
|
||||
{
|
||||
key: 'group-justify',
|
||||
title: '对齐',
|
||||
iconSvg:
|
||||
'<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
|
||||
menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
|
||||
},
|
||||
{
|
||||
key: 'group-indent',
|
||||
title: '缩进',
|
||||
iconSvg:
|
||||
'<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>',
|
||||
menuKeys: ['indent', 'delIndent'],
|
||||
},
|
||||
'|',
|
||||
'emotion',
|
||||
'insertLink',
|
||||
{
|
||||
key: 'group-image',
|
||||
title: '图片',
|
||||
iconSvg:
|
||||
'<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>',
|
||||
menuKeys: ['uploadImage'],
|
||||
},
|
||||
'insertTable',
|
||||
'codeBlock',
|
||||
'divider',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'fullScreen',
|
||||
];
|
||||
@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<div class="icon-selector">
|
||||
<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
|
||||
<div class="icon-selector w100 h100">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="fontIconWidth"
|
||||
trigger="click"
|
||||
transition="el-zoom-in-top"
|
||||
popper-class="icon-selector-popper"
|
||||
@show="onPopoverShow"
|
||||
>
|
||||
<template #reference>
|
||||
<el-input
|
||||
v-model="fontIconSearch"
|
||||
@ -23,8 +30,8 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
<transition name="el-zoom-in-top">
|
||||
<div class="icon-selector-warp" v-show="fontIconVisible">
|
||||
<template #default>
|
||||
<div class="icon-selector-warp">
|
||||
<div class="icon-selector-warp-title flex">
|
||||
<div class="flex-auto">{{ title }}</div>
|
||||
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
|
||||
@ -50,7 +57,7 @@
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
@ -58,6 +65,7 @@
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
|
||||
import initIconfont from '/@/utils/getStyleSheets';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'iconSelector',
|
||||
emits: ['update:modelValue', 'get', 'clear'],
|
||||
@ -102,8 +110,9 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: () => '无相关图标',
|
||||
},
|
||||
// 双向绑定值,字段名为固定,改了之后将不生效
|
||||
// 双向绑定值,默认为 modelValue,
|
||||
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
|
||||
// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
|
||||
modelValue: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
@ -111,7 +120,6 @@ export default defineComponent({
|
||||
const selectorScrollbarRef = ref();
|
||||
const state = reactive({
|
||||
fontIconPrefix: '',
|
||||
fontIconVisible: false,
|
||||
fontIconWidth: 0,
|
||||
fontIconSearch: '',
|
||||
fontIconTabsIndex: 0,
|
||||
@ -122,7 +130,6 @@ export default defineComponent({
|
||||
});
|
||||
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
|
||||
const onIconFocus = () => {
|
||||
state.fontIconVisible = true;
|
||||
if (!props.modelValue) return false;
|
||||
state.fontIconSearch = '';
|
||||
state.fontIconPlaceholder = props.modelValue;
|
||||
@ -136,10 +143,17 @@ export default defineComponent({
|
||||
};
|
||||
// 处理 icon 双向绑定数值回显
|
||||
const initModeValueEcho = () => {
|
||||
if (props.modelValue === '') return false;
|
||||
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
|
||||
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
|
||||
(<string | undefined>state.fontIconPrefix) = props.modelValue;
|
||||
};
|
||||
// 处理 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
|
||||
const initFontIconTypeEcho = () => {
|
||||
if ((<any>props.modelValue)?.indexOf('iconfont') > -1) onIconChange('ali');
|
||||
else if ((<any>props.modelValue)?.indexOf('ele-') > -1) onIconChange('ele');
|
||||
else if ((<any>props.modelValue)?.indexOf('fa') > -1) onIconChange('awe');
|
||||
else onIconChange('ali');
|
||||
};
|
||||
// 图标搜索及图标数据显示
|
||||
const fontIconSheetsFilterList = computed(() => {
|
||||
if (!state.fontIconSearch) return state.fontIconSheetsList;
|
||||
@ -183,8 +197,6 @@ export default defineComponent({
|
||||
state.fontIconPlaceholder = props.placeholder;
|
||||
// 初始化双向绑定回显
|
||||
initModeValueEcho();
|
||||
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
|
||||
selectorScrollbarRef.value.wrap$.scrollTop = 0;
|
||||
};
|
||||
// 图标点击切换
|
||||
const onIconChange = (type: string) => {
|
||||
@ -194,7 +206,6 @@ export default defineComponent({
|
||||
// 获取当前点击的 icon 图标
|
||||
const onColClick = (v: any) => {
|
||||
state.fontIconPlaceholder = v;
|
||||
state.fontIconVisible = false;
|
||||
state.fontIconPrefix = v;
|
||||
emit('get', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
@ -205,20 +216,18 @@ export default defineComponent({
|
||||
emit('clear', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 监听 Popover 打开,用于双向绑定值回显
|
||||
const onPopoverShow = () => {
|
||||
initModeValueEcho();
|
||||
initFontIconTypeEcho();
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// 判断默认进来是什么类型图标,进行 tab 回显
|
||||
if (props.type === 'all') {
|
||||
if ((<any>props.modelValue)?.indexOf('iconfont') > -1) onIconChange('ali');
|
||||
else if ((<any>props.modelValue)?.indexOf('ele-') > -1) onIconChange('ele');
|
||||
else if ((<any>props.modelValue)?.indexOf('fa') > -1) onIconChange('awe');
|
||||
else onIconChange('ali');
|
||||
} else {
|
||||
onIconChange(props.type);
|
||||
}
|
||||
initModeValueEcho();
|
||||
initResize();
|
||||
getInputWidth();
|
||||
});
|
||||
|
||||
// 监听双向绑定 modelValue 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
@ -235,6 +244,7 @@ export default defineComponent({
|
||||
onClearFontIcon,
|
||||
onIconFocus,
|
||||
onIconBlur,
|
||||
onPopoverShow,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'noticeBar',
|
||||
props: {
|
||||
@ -29,12 +30,12 @@ export default defineComponent({
|
||||
// 通知文本颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: () => 'var(--color-warning)',
|
||||
default: () => 'var(--el-color-warning)',
|
||||
},
|
||||
// 通知背景色
|
||||
background: {
|
||||
type: String,
|
||||
default: () => 'var(--color-warning-light-9)',
|
||||
default: () => 'var(--el-color-warning-light-9)',
|
||||
},
|
||||
// 字体大小,单位px
|
||||
size: {
|
||||
@ -171,7 +172,7 @@ export default defineComponent({
|
||||
.notice-bar-warp-slot {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
::v-deep(.el-carousel__item) {
|
||||
:deep(.el-carousel__item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
|
||||
<component :is="getIconName" />
|
||||
</i>
|
||||
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
|
||||
<img :src="getIconName" :style="setIconSvgInsStyle" />
|
||||
</div>
|
||||
<i v-else :class="getIconName" :style="setIconSvgStyle" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// 渲染函数:https://v3.cn.vuejs.org/guide/render-function.html
|
||||
import { h, resolveComponent } from 'vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface SvgIconProps {
|
||||
name: string;
|
||||
size: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'svgIcon',
|
||||
props: {
|
||||
// svg 图标组件名字
|
||||
@ -26,16 +28,46 @@ export default {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: SvgIconProps) {
|
||||
// 定义变量
|
||||
const linesString: string[] = ['https', 'http', '/src', '/assets'];
|
||||
const onLineStyle: string = `font-size: ${props.size}px;color: ${props.color}`;
|
||||
const localsStyle: string = `width: ${props.size}px;height: ${props.size}px`;
|
||||
setup(props) {
|
||||
// 在线链接、本地引入地址前缀
|
||||
const linesString = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];
|
||||
|
||||
// 逻辑判断
|
||||
if (props.name?.startsWith('ele-')) return () => h('i', { class: 'el-icon', style: onLineStyle }, [h(resolveComponent(props.name))]);
|
||||
else if (linesString.find((str) => props.name?.startsWith(str))) return () => h('img', { src: props.name, style: localsStyle });
|
||||
else return () => h('i', { class: props.name, style: onLineStyle });
|
||||
// 获取 icon 图标名称
|
||||
const getIconName = computed(() => {
|
||||
return props?.name;
|
||||
});
|
||||
// 用于判断 element plus 自带 svg 图标的显示、隐藏
|
||||
const isShowIconSvg = computed(() => {
|
||||
return props?.name?.startsWith('ele-');
|
||||
});
|
||||
// 用于判断在线链接、本地引入等图标显示、隐藏
|
||||
const isShowIconImg = computed(() => {
|
||||
return linesString.find((str) => props.name?.startsWith(str));
|
||||
});
|
||||
// 设置图标样式
|
||||
const setIconSvgStyle = computed(() => {
|
||||
return `font-size: ${props.size}px;color: ${props.color};`;
|
||||
});
|
||||
// 设置图片样式
|
||||
const setIconImgOutStyle = computed(() => {
|
||||
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
|
||||
});
|
||||
// 设置图片样式
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
|
||||
const setIconSvgInsStyle = computed(() => {
|
||||
const filterStyle: string[] = [];
|
||||
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
|
||||
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
|
||||
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
|
||||
});
|
||||
return {
|
||||
getIconName,
|
||||
isShowIconSvg,
|
||||
isShowIconImg,
|
||||
setIconSvgStyle,
|
||||
setIconImgOutStyle,
|
||||
setIconSvgInsStyle,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
|
||||
import enLocale from 'element-plus/lib/locale/lang/en';
|
||||
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
|
||||
import { store } from '/@/store/index';
|
||||
|
||||
import nextZhcn from '/@/i18n/lang/zh-cn';
|
||||
import nextEn from '/@/i18n/lang/en';
|
||||
@ -48,9 +50,18 @@ const messages = {
|
||||
},
|
||||
};
|
||||
|
||||
// 读取 pinia 默认语言
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
|
||||
// 导出语言国际化
|
||||
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
|
||||
export const i18n = createI18n({
|
||||
locale: store.state.themeConfig.themeConfig.globalI18n,
|
||||
silentTranslationWarn: true,
|
||||
missingWarn: false,
|
||||
silentFallbackWarn: true,
|
||||
fallbackWarn: false,
|
||||
locale: themeConfig.value.globalI18n,
|
||||
fallbackLocale: zhcnLocale.name,
|
||||
messages,
|
||||
});
|
||||
|
||||
@ -105,7 +105,6 @@ export default {
|
||||
logOutConfirm: 'determine',
|
||||
logOutCancel: 'cancel',
|
||||
logOutExit: 'Exiting',
|
||||
logOutSuccess: 'Exit successfully!',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: 'refresh',
|
||||
@ -172,7 +171,7 @@ export default {
|
||||
sixClassic: 'Two',
|
||||
sixTransverse: 'Three',
|
||||
sixColumns: 'Four',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.ts` It has been modified in.',
|
||||
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
|
||||
copyText: 'replication configuration',
|
||||
resetText: 'restore default',
|
||||
copyTextSuccess: 'Copy succeeded!',
|
||||
|
||||
@ -105,7 +105,6 @@ export default {
|
||||
logOutConfirm: '确定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
logOutSuccess: '安全退出成功!',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '刷新',
|
||||
@ -172,7 +171,7 @@ export default {
|
||||
sixClassic: '经典',
|
||||
sixTransverse: '横向',
|
||||
sixColumns: '分栏',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.ts` 中修改。',
|
||||
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
|
||||
copyText: '一键复制配置',
|
||||
resetText: '一键恢复默认',
|
||||
copyTextSuccess: '复制成功!',
|
||||
|
||||
@ -105,7 +105,6 @@ export default {
|
||||
logOutConfirm: '確定',
|
||||
logOutCancel: '取消',
|
||||
logOutExit: '退出中',
|
||||
logOutSuccess: '安全退出成功!',
|
||||
},
|
||||
tagsView: {
|
||||
refresh: '重繪',
|
||||
@ -172,7 +171,7 @@ export default {
|
||||
sixClassic: '經典',
|
||||
sixTransverse: '橫向',
|
||||
sixColumns: '分欄',
|
||||
tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.ts`中修改。',
|
||||
tipText: '點擊下方按鈕,複製佈局配寘去`src/stores/themeConfig.ts`中修改。',
|
||||
copyText: '一鍵複製配寘',
|
||||
resetText: '一鍵恢復默認',
|
||||
copyTextSuccess: '複製成功!',
|
||||
|
||||
@ -11,26 +11,32 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import Logo from '/@/layout/logo/index.vue';
|
||||
import Vertical from '/@/layout/navMenu/vertical.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutAside',
|
||||
components: { Logo, Vertical },
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const store = useStore();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const state = reactive({
|
||||
menuList: [],
|
||||
clientWidth: 0,
|
||||
});
|
||||
// 获取卡片全屏信息
|
||||
const isTagsViewCurrenFull = computed(() => {
|
||||
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
|
||||
});
|
||||
// 设置菜单展开/收起时的宽度
|
||||
const setCollapseStyle = computed(() => {
|
||||
const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
|
||||
const { layout, isCollapse, menuBar } = themeConfig.value;
|
||||
const asideBrTheme = ['#FFFFFF', '#FFF', '#fff', '#ffffff'];
|
||||
const asideBrColor = asideBrTheme.includes(menuBar) ? 'layout-el-aside-br-color' : '';
|
||||
// 判断是否是手机端
|
||||
@ -51,18 +57,12 @@ export default defineComponent({
|
||||
} else {
|
||||
if (layout === 'columns') {
|
||||
// 分栏布局,菜单收起时宽度给 1px
|
||||
if (isCollapse) {
|
||||
return [asideBrColor, 'layout-aside-pc-1'];
|
||||
} else {
|
||||
return [asideBrColor, 'layout-aside-pc-220'];
|
||||
}
|
||||
if (isCollapse) return [asideBrColor, 'layout-aside-pc-1'];
|
||||
else return [asideBrColor, 'layout-aside-pc-220'];
|
||||
} else {
|
||||
// 其它布局给 64px
|
||||
if (isCollapse) {
|
||||
return [asideBrColor, 'layout-aside-pc-64'];
|
||||
} else {
|
||||
return [asideBrColor, 'layout-aside-pc-220'];
|
||||
}
|
||||
if (isCollapse) return [asideBrColor, 'layout-aside-pc-64'];
|
||||
else return [asideBrColor, 'layout-aside-pc-220'];
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -74,21 +74,21 @@ export default defineComponent({
|
||||
el?.parentNode?.removeChild(el);
|
||||
}, 300);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) store.state.themeConfig.themeConfig.isCollapse = false;
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
document.body.setAttribute('class', '');
|
||||
};
|
||||
// 设置显示/隐藏 logo
|
||||
const setShowLogo = computed(() => {
|
||||
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isShowLogo } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
|
||||
});
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
|
||||
(state.menuList as any) = filterRoutesFun(store.state.routesList.routesList);
|
||||
if (themeConfig.value.layout === 'columns') return false;
|
||||
(state.menuList as any) = filterRoutesFun(routesList.value);
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<object>) => {
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
@ -103,24 +103,30 @@ export default defineComponent({
|
||||
};
|
||||
// 鼠标移入、移出
|
||||
const onAsideEnterLeave = (bool: Boolean) => {
|
||||
let { layout } = store.state.themeConfig.themeConfig;
|
||||
let { layout } = themeConfig.value;
|
||||
if (layout !== 'columns') return false;
|
||||
if (!bool) proxy.mittBus.emit('restoreDefault');
|
||||
store.dispatch('routesList/setColumnsMenuHover', bool);
|
||||
stores.setColumnsMenuHover(bool);
|
||||
};
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
||||
watch(themeConfig.value, (val) => {
|
||||
if (val.isShowLogoChange !== val.isShowLogo) {
|
||||
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
|
||||
proxy.$refs.layoutAsideScrollbarRef.update();
|
||||
}
|
||||
});
|
||||
// 监听vuex值的变化,动态赋值给菜单中
|
||||
watch(store.state, (val) => {
|
||||
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
||||
if (layout === 'classic' && isClassicSplitMenu) return false;
|
||||
setFilterRoutes();
|
||||
});
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
||||
if (layout === 'classic' && isClassicSplitMenu) return false;
|
||||
setFilterRoutes();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
initMenuFixed(document.body.clientWidth);
|
||||
@ -131,7 +137,7 @@ export default defineComponent({
|
||||
state.menuList = res.children;
|
||||
});
|
||||
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = [];
|
||||
state.menuList = res.children;
|
||||
|
||||
@ -15,39 +15,42 @@
|
||||
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
|
||||
:title="$t(v.meta.title)"
|
||||
>
|
||||
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
|
||||
<div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
|
||||
<SvgIcon :name="v.meta.icon" />
|
||||
<div class="columns-vertical-title font12">
|
||||
{{
|
||||
$t(v.meta.title) && $t(v.meta.title).length >= 4
|
||||
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
|
||||
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
|
||||
: $t(v.meta.title)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div :class="setColumnsAsidelayout" v-else>
|
||||
<div :class="themeConfig.columnsAsideLayout" v-else>
|
||||
<a :href="v.meta.isLink" target="_blank">
|
||||
<SvgIcon :name="v.meta.icon" />
|
||||
<div class="columns-vertical-title font12">
|
||||
{{
|
||||
$t(v.meta.title) && $t(v.meta.title).length >= 4
|
||||
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
|
||||
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
|
||||
: $t(v.meta.title)
|
||||
}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<div ref="columnsAsideActiveRef" :class="setColumnsAsideStyle"></div>
|
||||
<div ref="columnsAsideActiveRef" :class="themeConfig.columnsAsideStyle"></div>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
|
||||
import { reactive, toRefs, ref, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface ColumnsAsideState {
|
||||
@ -58,7 +61,6 @@ interface ColumnsAsideState {
|
||||
liOldPath: null | string;
|
||||
difference: number;
|
||||
routeSplit: string[];
|
||||
isNavHover: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@ -67,7 +69,10 @@ export default defineComponent({
|
||||
const columnsAsideOffsetTopRefs: any = ref([]);
|
||||
const columnsAsideActiveRef = ref();
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const store = useStore();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<ColumnsAsideState>({
|
||||
@ -78,15 +83,6 @@ export default defineComponent({
|
||||
liOldPath: null,
|
||||
difference: 0,
|
||||
routeSplit: [],
|
||||
isNavHover: false,
|
||||
});
|
||||
// 设置分栏高亮风格
|
||||
const setColumnsAsideStyle = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.columnsAsideStyle;
|
||||
});
|
||||
// 设置分栏布局风格
|
||||
const setColumnsAsidelayout = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.columnsAsideLayout;
|
||||
});
|
||||
// 设置菜单高亮位置移动
|
||||
const setColumnsAsideMove = (k: number) => {
|
||||
@ -107,19 +103,16 @@ export default defineComponent({
|
||||
state.liOldIndex = k;
|
||||
state.liHoverIndex = k;
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
|
||||
store.dispatch('routesList/setColumnsMenuHover', false);
|
||||
store.dispatch('routesList/setColumnsNavHover', true);
|
||||
state.isNavHover = true;
|
||||
stores.setColumnsMenuHover(false);
|
||||
stores.setColumnsNavHover(true);
|
||||
};
|
||||
// 鼠标移走时,显示原来的子级菜单
|
||||
const onColumnsAsideMenuMouseleave = async () => {
|
||||
await store.dispatch('routesList/setColumnsNavHover', false);
|
||||
await stores.setColumnsNavHover(false);
|
||||
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
|
||||
setTimeout(() => {
|
||||
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
|
||||
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
|
||||
}, 100);
|
||||
// state.isNavHover = false;
|
||||
};
|
||||
// 设置高亮动态位置
|
||||
const onColumnsAsideDown = (k: number) => {
|
||||
@ -129,7 +122,7 @@ export default defineComponent({
|
||||
};
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
|
||||
state.columnsAsideList = filterRoutesFun(routesList.value);
|
||||
const resData: any = setSendChildren(route.path);
|
||||
if (Object.keys(resData).length <= 0) return false;
|
||||
onColumnsAsideDown(resData.item[0].k);
|
||||
@ -150,7 +143,7 @@ export default defineComponent({
|
||||
return currentData;
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<object>) => {
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
@ -172,17 +165,23 @@ export default defineComponent({
|
||||
}, 0);
|
||||
};
|
||||
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
|
||||
watch(store.state, (val) => {
|
||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
|
||||
state.liHoverIndex = null;
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
|
||||
} else {
|
||||
state.liHoverIndex = state.liOldIndex;
|
||||
if (!state.liOldPath) return false;
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
|
||||
state.liHoverIndex = null;
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
|
||||
} else {
|
||||
state.liHoverIndex = state.liOldIndex;
|
||||
if (!state.liOldPath) return false;
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
});
|
||||
);
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
setFilterRoutes();
|
||||
@ -202,11 +201,10 @@ export default defineComponent({
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
|
||||
});
|
||||
return {
|
||||
themeConfig,
|
||||
columnsAsideOffsetTopRefs,
|
||||
columnsAsideActiveRef,
|
||||
onColumnsAsideDown,
|
||||
setColumnsAsideStyle,
|
||||
setColumnsAsidelayout,
|
||||
onColumnsAsideMenuClick,
|
||||
onColumnsAsideMenuMouseenter,
|
||||
onColumnsAsideMenuMouseleave,
|
||||
@ -260,7 +258,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
.layout-columns-active {
|
||||
color: var(--el-color-white);
|
||||
color: var(--next-bg-columnsMenuBarColor) !important;
|
||||
transition: 0.3s ease-in-out;
|
||||
}
|
||||
.layout-columns-hover {
|
||||
|
||||
@ -6,23 +6,25 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import NavBarsIndex from '/@/layout/navBars/index.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutHeader',
|
||||
components: { NavBarsIndex },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
// 设置 header 的高度
|
||||
const setHeaderHeight = computed(() => {
|
||||
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsview, layout } = themeConfig.value;
|
||||
if (isTagsview && layout !== 'classic') return '84px';
|
||||
else return '50px';
|
||||
});
|
||||
// 获取卡片全屏信息
|
||||
const isTagsViewCurrenFull = computed(() => {
|
||||
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
|
||||
});
|
||||
return {
|
||||
setHeaderHeight,
|
||||
isTagsViewCurrenFull,
|
||||
|
||||
@ -1,20 +1,30 @@
|
||||
<template>
|
||||
<el-main class="layout-main">
|
||||
<el-scrollbar
|
||||
class="layout-scrollbar"
|
||||
ref="layoutScrollbarRef"
|
||||
:style="{ padding: currentRouteMeta.isLink && currentRouteMeta.isIframe ? 0 : '', transition: 'padding 0.3s ease-in-out' }"
|
||||
:class="{
|
||||
'layout-scrollbar':
|
||||
(!isClassicOrTransverse && !currentRouteMeta.isLink && !currentRouteMeta.isIframe) ||
|
||||
(!isClassicOrTransverse && currentRouteMeta.isLink && !currentRouteMeta.isIframe),
|
||||
}"
|
||||
>
|
||||
<LayoutParentView :style="{ minHeight: `calc(100vh - ${headerHeight})` }" />
|
||||
<Footer v-if="getThemeConfig.isFooter" />
|
||||
<LayoutParentView
|
||||
:style="{
|
||||
padding: !isClassicOrTransverse || (currentRouteMeta.isLink && currentRouteMeta.isIframe) ? '0' : '15px',
|
||||
transition: 'padding 0.3s ease-in-out',
|
||||
}"
|
||||
/>
|
||||
<Footer v-if="themeConfig.isFooter" />
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import LayoutParentView from '/@/layout/routerView/parent.vue';
|
||||
import Footer from '/@/layout/footer/index.vue';
|
||||
|
||||
@ -29,22 +39,24 @@ export default defineComponent({
|
||||
components: { LayoutParentView, Footer },
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const state = reactive<MainState>({
|
||||
headerHeight: '',
|
||||
currentRouteMeta: {},
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
// 判断布局
|
||||
const isClassicOrTransverse = computed(() => {
|
||||
const { layout } = themeConfig.value;
|
||||
return layout === 'classic' || layout === 'transverse';
|
||||
});
|
||||
// 设置 main 的高度
|
||||
const initHeaderHeight = () => {
|
||||
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
if (isTagsview) return (state.headerHeight = bool ? `85px` : `114px`);
|
||||
else return (state.headerHeight = `51px`);
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsview) return (state.headerHeight = bool ? `86px` : `115px`);
|
||||
else return (state.headerHeight = `80px`);
|
||||
};
|
||||
// 初始化获取当前路由 meta,用于设置 iframes padding
|
||||
const initGetMeta = () => {
|
||||
@ -54,6 +66,7 @@ export default defineComponent({
|
||||
onMounted(async () => {
|
||||
await initGetMeta();
|
||||
initHeaderHeight();
|
||||
NextLoading.done();
|
||||
});
|
||||
// 监听路由变化
|
||||
watch(
|
||||
@ -61,22 +74,26 @@ export default defineComponent({
|
||||
() => {
|
||||
state.currentRouteMeta = route.meta;
|
||||
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
|
||||
state.headerHeight = bool ? `85px` : `114px`;
|
||||
state.headerHeight = bool ? `86px` : `115px`;
|
||||
proxy.$refs.layoutScrollbarRef.update();
|
||||
}
|
||||
);
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
||||
state.currentRouteMeta = route.meta;
|
||||
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
|
||||
state.headerHeight = val.isTagsview ? (bool ? `85px` : `114px`) : '51px';
|
||||
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
||||
if (!proxy.$refs.layoutScrollbarRef) return false;
|
||||
proxy.$refs.layoutScrollbarRef.update();
|
||||
watch(
|
||||
themeConfig,
|
||||
(val) => {
|
||||
state.currentRouteMeta = route.meta;
|
||||
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
|
||||
state.headerHeight = val.isTagsview ? (bool ? `86px` : `115px`) : '51px';
|
||||
proxy.$refs?.layoutScrollbarRef?.update();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
});
|
||||
);
|
||||
return {
|
||||
getThemeConfig,
|
||||
themeConfig,
|
||||
isClassicOrTransverse,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
import { onBeforeRouteUpdate } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutFooter',
|
||||
setup() {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<component :is="getThemeConfig.layout" />
|
||||
<component :is="themeConfig.layout" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layout',
|
||||
components: {
|
||||
@ -16,24 +18,21 @@ export default defineComponent({
|
||||
},
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 窗口大小改变时(适配移动端)
|
||||
const onLayoutResize = () => {
|
||||
if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
|
||||
if (!Local.get('oldLayout')) Local.set('oldLayout', themeConfig.value.layout);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) {
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isCollapse = false;
|
||||
proxy.mittBus.emit('layoutMobileResize', {
|
||||
layout: 'defaults',
|
||||
clientWidth,
|
||||
});
|
||||
} else {
|
||||
proxy.mittBus.emit('layoutMobileResize', {
|
||||
layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
|
||||
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
|
||||
clientWidth,
|
||||
});
|
||||
}
|
||||
@ -48,7 +47,7 @@ export default defineComponent({
|
||||
window.removeEventListener('resize', onLayoutResize);
|
||||
});
|
||||
return {
|
||||
getThemeConfig,
|
||||
themeConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
|
||||
<div class="layout-lock-screen-login-box">
|
||||
<div class="layout-lock-screen-login-box-img">
|
||||
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg" />
|
||||
<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
|
||||
</div>
|
||||
<div class="layout-lock-screen-login-box-name">Administrator</div>
|
||||
<div class="layout-lock-screen-login-box-value">
|
||||
@ -49,9 +49,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-lock-screen-login-icon">
|
||||
<SvgIcon name="ele-Microphone" />
|
||||
<SvgIcon name="ele-AlarmClock" />
|
||||
<SvgIcon name="ele-SwitchButton" />
|
||||
<SvgIcon name="ele-Microphone" :size="20" />
|
||||
<SvgIcon name="ele-AlarmClock" :size="20" />
|
||||
<SvgIcon name="ele-SwitchButton" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@ -61,9 +61,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface LockScreenState {
|
||||
@ -89,7 +90,8 @@ export default defineComponent({
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const layoutLockScreenInputRef = ref();
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const state = reactive<LockScreenState>({
|
||||
transparency: 1,
|
||||
downClientY: 0,
|
||||
@ -166,14 +168,14 @@ export default defineComponent({
|
||||
};
|
||||
// 锁屏时间定时器
|
||||
const initLockScreen = () => {
|
||||
if (store.state.themeConfig.themeConfig.isLockScreen) {
|
||||
if (themeConfig.value.isLockScreen) {
|
||||
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
||||
if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
|
||||
if (themeConfig.value.lockScreenTime <= 1) {
|
||||
state.isShowLockScreen = true;
|
||||
setLocalThemeConfig();
|
||||
return false;
|
||||
}
|
||||
store.state.themeConfig.themeConfig.lockScreenTime--;
|
||||
themeConfig.value.lockScreenTime--;
|
||||
}, 1000);
|
||||
} else {
|
||||
clearInterval(state.isShowLockScreenIntervalTime);
|
||||
@ -181,13 +183,13 @@ export default defineComponent({
|
||||
};
|
||||
// 存储布局配置
|
||||
const setLocalThemeConfig = () => {
|
||||
store.state.themeConfig.themeConfig.isDrawer = false;
|
||||
Local.set('themeConfig', store.state.themeConfig.themeConfig);
|
||||
themeConfig.value.isDrawer = false;
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
};
|
||||
// 密码输入点击事件
|
||||
const onLockScreenSubmit = () => {
|
||||
store.state.themeConfig.themeConfig.isLockScreen = false;
|
||||
store.state.themeConfig.themeConfig.lockScreenTime = 30;
|
||||
themeConfig.value.isLockScreen = false;
|
||||
themeConfig.value.lockScreenTime = 30;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 页面加载时
|
||||
@ -231,7 +233,7 @@ export default defineComponent({
|
||||
}
|
||||
.layout-lock-screen-img {
|
||||
@extend .layout-lock-screen-fixed;
|
||||
background-image: url('https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/03.jpg');
|
||||
background-image: url('https://img-blog.csdnimg.cn/afa9c317667f47d5bea34b85af45979e.png#pic_center');
|
||||
background-size: 100% 100%;
|
||||
z-index: 9999991;
|
||||
}
|
||||
@ -253,11 +255,11 @@ export default defineComponent({
|
||||
bottom: 50px;
|
||||
&-time {
|
||||
font-size: 100px;
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
&-info {
|
||||
font-size: 40px;
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
&-minutes {
|
||||
font-size: 16px;
|
||||
@ -270,7 +272,7 @@ export default defineComponent({
|
||||
border-radius: 100%;
|
||||
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
@ -286,7 +288,7 @@ export default defineComponent({
|
||||
position: absolute;
|
||||
top: 150%;
|
||||
font-size: 12px;
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
left: 50%;
|
||||
line-height: 1.2;
|
||||
transform: translate(-50%, -50%);
|
||||
@ -297,7 +299,7 @@ export default defineComponent({
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease;
|
||||
i {
|
||||
@ -322,7 +324,7 @@ export default defineComponent({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: var(--color-whites);
|
||||
color: var(--el-color-white);
|
||||
&-box {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
@ -357,11 +359,11 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep(.el-input-group__append) {
|
||||
:deep(.el-input-group__append) {
|
||||
background: var(--el-color-white);
|
||||
padding: 0px 15px;
|
||||
}
|
||||
::v-deep(.el-input__inner) {
|
||||
:deep(.el-input__inner) {
|
||||
border-right-color: var(--el-border-color-extra-light);
|
||||
&:hover {
|
||||
border-color: var(--el-border-color-extra-light);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
||||
<img :src="logoMini" class="layout-logo-medium-img" />
|
||||
<span>{{ getThemeConfig.globalTitle }}</span>
|
||||
<span>{{ themeConfig.globalTitle }}</span>
|
||||
</div>
|
||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||
<img :src="logoMini" class="layout-logo-size-img" />
|
||||
@ -10,32 +10,30 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
import logoMini from '/@/assets/logo-mini.svg';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutLogo',
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 设置 logo 的显示。classic 经典布局默认显示 logo
|
||||
const setShowLogo = computed(() => {
|
||||
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isCollapse, layout } = themeConfig.value;
|
||||
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
|
||||
});
|
||||
// logo 点击实现菜单展开/收起
|
||||
const onThemeConfigChange = () => {
|
||||
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
|
||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
||||
if (themeConfig.value.layout === 'transverse') return false;
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
};
|
||||
return {
|
||||
logoMini,
|
||||
setShowLogo,
|
||||
getThemeConfig,
|
||||
themeConfig,
|
||||
onThemeConfigChange,
|
||||
};
|
||||
},
|
||||
@ -54,6 +52,10 @@ export default defineComponent({
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
span {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover {
|
||||
span {
|
||||
color: var(--color-primary-light-2);
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<el-container class="layout-mian-height-50">
|
||||
<Aside />
|
||||
<div class="flex-center layout-backtop">
|
||||
<TagsView v-if="getThemeConfig.isTagsview" />
|
||||
<TagsView v-if="themeConfig.isTagsview" />
|
||||
<Main />
|
||||
</div>
|
||||
</el-container>
|
||||
@ -13,23 +13,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
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 defineComponent({
|
||||
name: 'layoutClassic',
|
||||
components: { Aside, Header, Main, TagsView },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
return {
|
||||
getThemeConfig,
|
||||
themeConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -17,18 +17,21 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
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 defineComponent({
|
||||
name: 'layoutColumns',
|
||||
components: { Aside, Header, Main, ColumnsAside },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const isFixedHeader = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
||||
return themeConfig.value.isFixedHeader;
|
||||
});
|
||||
return {
|
||||
isFixedHeader,
|
||||
|
||||
@ -15,19 +15,22 @@
|
||||
<script lang="ts">
|
||||
import { computed, getCurrentInstance, watch, defineComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import Aside from '/@/layout/component/aside.vue';
|
||||
import Header from '/@/layout/component/header.vue';
|
||||
import Main from '/@/layout/component/main.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutDefaults',
|
||||
components: { Aside, Header, Main },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const isFixedHeader = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
||||
return themeConfig.value.isFixedHeader;
|
||||
});
|
||||
// 监听路由的变化
|
||||
watch(
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<script lang="ts">
|
||||
import Header from '/@/layout/component/header.vue';
|
||||
import Main from '/@/layout/component/main.vue';
|
||||
|
||||
export default {
|
||||
name: 'layoutTransverse',
|
||||
components: { Header, Main },
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
|
||||
<div v-if="isShowBreadcrumb" class="layout-navbars-breadcrumb">
|
||||
<SvgIcon
|
||||
class="layout-navbars-breadcrumb-icon"
|
||||
:name="getThemeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"
|
||||
:name="themeConfig.isCollapse ? 'ele-Expand' : 'ele-Fold'"
|
||||
:size="16"
|
||||
@click="onThemeConfigChange"
|
||||
/>
|
||||
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
||||
<transition-group name="breadcrumb" mode="out-in">
|
||||
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="!v.meta.tagsViewName ? v.meta.title : v.meta.tagsViewName">
|
||||
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<div v-if="!v.meta.tagsViewName">{{ $t(v.meta.title) }}</div>
|
||||
<div v-else>{{ v.meta.tagsViewName }}</div>
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.meta.title) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
@ -24,8 +26,11 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import other from '/@/utils/other';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface BreadcrumbState {
|
||||
@ -38,7 +43,10 @@ interface BreadcrumbState {
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumb',
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<BreadcrumbState>({
|
||||
@ -47,16 +55,12 @@ export default defineComponent({
|
||||
routeSplitFirst: '',
|
||||
routeSplitIndex: 1,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
// 动态设置经典、横向布局不显示
|
||||
const isShowBreadcrumb = computed(() => {
|
||||
initRouteSplit(route.path);
|
||||
const { layout, isBreadcrumb } = store.state.themeConfig.themeConfig;
|
||||
if (layout === 'classic' || layout === 'transverse') return 'none';
|
||||
else return isBreadcrumb ? '' : 'none';
|
||||
const { layout, isBreadcrumb } = themeConfig.value;
|
||||
if (layout === 'classic' || layout === 'transverse') return false;
|
||||
else return isBreadcrumb ? true : false;
|
||||
});
|
||||
// 面包屑点击时
|
||||
const onBreadcrumbClick = (v: any) => {
|
||||
@ -66,18 +70,18 @@ export default defineComponent({
|
||||
};
|
||||
// 展开/收起左侧菜单点击
|
||||
const onThemeConfigChange = () => {
|
||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 存储布局配置
|
||||
const setLocalThemeConfig = () => {
|
||||
Local.remove('themeConfig');
|
||||
Local.set('themeConfig', getThemeConfig.value);
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
};
|
||||
// 处理面包屑数据
|
||||
const getBreadcrumbList = (arr: Array<object>) => {
|
||||
arr.map((item: any) => {
|
||||
state.routeSplit.map((v: any, k: number, arrs: any) => {
|
||||
const getBreadcrumbList = (arr: Array<string>) => {
|
||||
arr.forEach((item: any) => {
|
||||
state.routeSplit.forEach((v: any, k: number, arrs: any) => {
|
||||
if (state.routeSplitFirst === item.path) {
|
||||
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
|
||||
state.breadcrumbList.push(item);
|
||||
@ -89,13 +93,15 @@ export default defineComponent({
|
||||
};
|
||||
// 当前路由字符串切割成数组,并删除第一项空内容
|
||||
const initRouteSplit = (path: string) => {
|
||||
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
|
||||
state.breadcrumbList = [store.state.routesList.routesList[0]];
|
||||
if (!themeConfig.value.isBreadcrumb) return false;
|
||||
state.breadcrumbList = [routesList.value[0]];
|
||||
state.routeSplit = path.split('/');
|
||||
state.routeSplit.shift();
|
||||
state.routeSplitFirst = `/${state.routeSplit[0]}`;
|
||||
state.routeSplitIndex = 1;
|
||||
getBreadcrumbList(store.state.routesList.routesList);
|
||||
getBreadcrumbList(routesList.value);
|
||||
if (route.name === 'home' || (route.name === 'notFound' && state.breadcrumbList.length > 1)) state.breadcrumbList.shift();
|
||||
if (state.breadcrumbList.length > 0) state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(route);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@ -108,7 +114,7 @@ export default defineComponent({
|
||||
return {
|
||||
onThemeConfigChange,
|
||||
isShowBreadcrumb,
|
||||
getThemeConfig,
|
||||
themeConfig,
|
||||
onBreadcrumbClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
@ -122,14 +128,19 @@ export default defineComponent({
|
||||
height: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
.layout-navbars-breadcrumb-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
margin-right: 15px;
|
||||
color: var(--next-bg-topBarColor);
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.layout-navbars-breadcrumb-span {
|
||||
display: flex;
|
||||
opacity: 0.7;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
@ -137,11 +148,11 @@ export default defineComponent({
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
::v-deep(.el-breadcrumb__separator) {
|
||||
:deep(.el-breadcrumb__separator) {
|
||||
opacity: 0.7;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
::v-deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
|
||||
:deep(.el-breadcrumb__inner a, .el-breadcrumb__inner.is-link) {
|
||||
font-weight: unset !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
&:hover {
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
<template>
|
||||
<div class="layout-navbars-close-full" v-if="isTagsViewCurrenFull">
|
||||
<div class="layout-navbars-close-full-box" :title="$t('message.tagsView.closeFullscreen')" @click="onCloseFullscreen">
|
||||
<SvgIcon name="ele-Close" />
|
||||
<div class="layout-navbars-close-full-icon">
|
||||
<SvgIcon name="ele-Close" :title="$t('message.tagsView.closeFullscreen')" @click="onCloseFullscreen" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutCloseFull',
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 获取卡片全屏信息
|
||||
const isTagsViewCurrenFull = computed(() => {
|
||||
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
|
||||
});
|
||||
const stores = useTagsViewRoutes();
|
||||
const { isTagsViewCurrenFull } = storeToRefs(stores);
|
||||
// 关闭当前全屏
|
||||
const onCloseFullscreen = () => {
|
||||
store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
|
||||
stores.setCurrenFullscreen(false);
|
||||
};
|
||||
return {
|
||||
isTagsViewCurrenFull,
|
||||
@ -35,28 +34,27 @@ export default defineComponent({
|
||||
z-index: 9999999999;
|
||||
right: -30px;
|
||||
top: -30px;
|
||||
.layout-navbars-close-full-box {
|
||||
.layout-navbars-close-full-icon {
|
||||
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: relative;
|
||||
:deep(i) {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
left: 10px;
|
||||
top: 35px;
|
||||
color: #333333;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
:deep(i) {
|
||||
color: var(--el-color-primary);
|
||||
transition: all 0.3s ease;
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||
import User from '/@/layout/navBars/breadcrumb/user.vue';
|
||||
import Logo from '/@/layout/logo/index.vue';
|
||||
@ -26,30 +28,33 @@ export default defineComponent({
|
||||
components: { Breadcrumb, User, Logo, Horizontal },
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const store = useStore();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const route = useRoute();
|
||||
const state = reactive<IndexState>({
|
||||
menuList: [],
|
||||
});
|
||||
// 设置 logo 显示/隐藏
|
||||
const setIsShowLogo = computed(() => {
|
||||
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isShowLogo, layout } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
|
||||
});
|
||||
// 设置是否显示横向导航菜单
|
||||
const isLayoutTransverse = computed(() => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
|
||||
});
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
|
||||
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
|
||||
const resData = setSendClassicChildren(route.path);
|
||||
proxy.mittBus.emit('setSendClassicChildren', resData);
|
||||
} else {
|
||||
state.menuList = filterRoutesFun(store.state.routesList.routesList);
|
||||
state.menuList = filterRoutesFun(routesList.value);
|
||||
}
|
||||
};
|
||||
// 设置了分割菜单时,删除底下 children
|
||||
@ -60,7 +65,7 @@ export default defineComponent({
|
||||
return arr;
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<object>) => {
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
@ -73,7 +78,7 @@ export default defineComponent({
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
||||
filterRoutesFun(routesList.value).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
@ -92,7 +97,7 @@ export default defineComponent({
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
|
||||
});
|
||||
return {
|
||||
setIsShowLogo,
|
||||
@ -108,7 +113,6 @@ export default defineComponent({
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 15px;
|
||||
background: var(--next-bg-topBar);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
}
|
||||
|
||||
@ -29,7 +29,8 @@
|
||||
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface SearchState {
|
||||
@ -47,9 +48,10 @@ interface Restaurant {
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbSearch',
|
||||
setup() {
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
|
||||
const layoutMenuAutocompleteRef = ref();
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const state = reactive<SearchState>({
|
||||
isShowSearch: false,
|
||||
@ -62,7 +64,9 @@ export default defineComponent({
|
||||
state.isShowSearch = true;
|
||||
initTageView();
|
||||
nextTick(() => {
|
||||
layoutMenuAutocompleteRef.value.focus();
|
||||
setTimeout(() => {
|
||||
layoutMenuAutocompleteRef.value.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
// 搜索弹窗关闭
|
||||
@ -87,7 +91,7 @@ export default defineComponent({
|
||||
// 初始化菜单数据
|
||||
const initTageView = () => {
|
||||
if (state.tagsViewList.length > 0) return false;
|
||||
store.state.tagsViewRoutes.tagsViewRoutes.map((v: any) => {
|
||||
tagsViewRoutes.value.map((v: any) => {
|
||||
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
});
|
||||
};
|
||||
@ -118,12 +122,12 @@ export default defineComponent({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-search-dialog {
|
||||
::v-deep(.el-dialog) {
|
||||
:deep(.el-dialog) {
|
||||
box-shadow: unset !important;
|
||||
border-radius: 0 !important;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
::v-deep(.el-autocomplete) {
|
||||
:deep(.el-autocomplete) {
|
||||
width: 560px;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
|
||||
@ -108,16 +108,26 @@
|
||||
|
||||
<!-- 界面设置 -->
|
||||
<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isCollapse" size="small" @change="onThemeConfigChange"></el-switch>
|
||||
<el-switch
|
||||
v-model="getThemeConfig.isCollapse"
|
||||
:disabled="getThemeConfig.layout === 'transverse'"
|
||||
size="small"
|
||||
@change="onThemeConfigChange"
|
||||
></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isUniqueOpened" size="small" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch
|
||||
v-model="getThemeConfig.isUniqueOpened"
|
||||
:disabled="getThemeConfig.layout === 'transverse'"
|
||||
size="small"
|
||||
@change="setLocalThemeConfig"
|
||||
></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
@ -399,29 +409,37 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { getLightColor } from '/@/utils/theme';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { getLightColor, getDarkColor } from '/@/utils/theme';
|
||||
import { verifyAndSpace } from '/@/utils/toolsValidate';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import Watermark from '/@/utils/wartermark';
|
||||
import commonFunction from '/@/utils/commonFunction';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbSeting',
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { copyText } = commonFunction();
|
||||
const state = reactive({
|
||||
isMobile: false,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 1、全局主题
|
||||
const onColorPickerChange = () => {
|
||||
if (!getThemeConfig.value.primary) return ElMessage.warning('全局主题 primary 颜色值不能为空');
|
||||
// 颜色加深
|
||||
document.documentElement.style.setProperty('--el-color-primary-dark-2', `${getDarkColor(getThemeConfig.value.primary, 0.1)}`);
|
||||
document.documentElement.style.setProperty('--el-color-primary', getThemeConfig.value.primary);
|
||||
// 颜色变浅
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(getThemeConfig.value.primary, i / 10)}`);
|
||||
}
|
||||
@ -430,6 +448,9 @@ export default defineComponent({
|
||||
// 2、菜单 / 顶栏
|
||||
const onBgColorPickerChange = (bg: string) => {
|
||||
document.documentElement.style.setProperty(`--next-bg-${bg}`, (<any>getThemeConfig.value)[bg]);
|
||||
if (bg === 'menuBar') {
|
||||
document.documentElement.style.setProperty(`--next-bg-menuBar-light-1`, <any>getLightColor(getThemeConfig.value.menuBar, 0.05));
|
||||
}
|
||||
onTopBarGradualChange();
|
||||
onMenuBarGradualChange();
|
||||
onColumnsMenuBarGradualChange();
|
||||
@ -520,7 +541,7 @@ export default defineComponent({
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 4、界面显示 --> 水印文案
|
||||
const onWartermarkTextInput = (val: string) => {
|
||||
const onWartermarkTextInput = (val: any) => {
|
||||
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
|
||||
if (getThemeConfig.value.wartermarkText === '') return false;
|
||||
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
|
||||
@ -530,6 +551,7 @@ export default defineComponent({
|
||||
const onSetLayout = (layout: string) => {
|
||||
Local.set('oldLayout', layout);
|
||||
if (getThemeConfig.value.layout === layout) return false;
|
||||
if (layout === 'transverse') getThemeConfig.value.isCollapse = false;
|
||||
getThemeConfig.value.layout = layout;
|
||||
getThemeConfig.value.isDrawer = false;
|
||||
initLayoutChangeFun();
|
||||
@ -603,6 +625,8 @@ export default defineComponent({
|
||||
state.isMobile = other.isMobile();
|
||||
});
|
||||
setTimeout(() => {
|
||||
// 默认样式
|
||||
onColorPickerChange();
|
||||
// 灰色模式
|
||||
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
|
||||
// 色弱模式
|
||||
@ -619,7 +643,7 @@ export default defineComponent({
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('layoutMobileResize');
|
||||
proxy.mittBus.off('layoutMobileResize', () => {});
|
||||
});
|
||||
return {
|
||||
openDrawer,
|
||||
@ -655,7 +679,7 @@ export default defineComponent({
|
||||
.layout-breadcrumb-seting-bar {
|
||||
height: calc(100vh - 50px);
|
||||
padding: 0 15px;
|
||||
::v-deep(.el-scrollbar__view) {
|
||||
:deep(.el-scrollbar__view) {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.layout-breadcrumb-seting-bar-flex {
|
||||
@ -713,7 +737,7 @@ export default defineComponent({
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px solid;
|
||||
border-color: var(--el-color-primary-light-4);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
border-radius: 100%;
|
||||
padding: 4px;
|
||||
.layout-tips-box {
|
||||
@ -722,7 +746,7 @@ export default defineComponent({
|
||||
height: 30px;
|
||||
z-index: 9;
|
||||
border: 1px solid;
|
||||
border-color: var(--el-color-primary-light-4);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
border-radius: 100%;
|
||||
.layout-tips-txt {
|
||||
transition: inherit;
|
||||
@ -732,7 +756,7 @@ export default defineComponent({
|
||||
line-height: 1;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
color: var(--el-color-primary-light-4);
|
||||
color: var(--el-color-primary-light-5);
|
||||
text-align: center;
|
||||
transform: rotate(30deg);
|
||||
left: -1px;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
|
||||
<div class="layout-navbars-breadcrumb-user pr15" :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>
|
||||
@ -33,7 +33,7 @@
|
||||
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<el-popover placement="bottom" trigger="click" :width="300">
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :is-dot="true">
|
||||
<el-icon :title="$t('message.user.title4')">
|
||||
@ -41,7 +41,9 @@
|
||||
</el-icon>
|
||||
</el-badge>
|
||||
</template>
|
||||
<UserNews />
|
||||
<template #default>
|
||||
<UserNews />
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
|
||||
@ -53,8 +55,8 @@
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ getUserInfos.userName === '' ? 'common' : getUserInfos.userName }}
|
||||
<img :src="userInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ userInfos.userName === '' ? 'common' : userInfos.userName }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
</el-icon>
|
||||
@ -80,12 +82,14 @@ import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { resetRoute } from '/@/router/index';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
|
||||
import Search from '/@/layout/navBars/breadcrumb/search.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbUser',
|
||||
components: { UserNews, Search },
|
||||
@ -93,25 +97,20 @@ export default defineComponent({
|
||||
const { t } = useI18n();
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const stores = useUserInfo();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const searchRef = ref();
|
||||
const state = reactive({
|
||||
isScreenfull: false,
|
||||
disabledI18n: 'zh-cn',
|
||||
disabledSize: 'large',
|
||||
});
|
||||
// 获取用户信息 vuex
|
||||
const getUserInfos = computed(() => {
|
||||
return <any>store.state.userInfos.userInfos;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
// 设置分割样式
|
||||
const layoutUserFlexNum = computed(() => {
|
||||
let num: string | number = '';
|
||||
const { layout, isClassicSplitMenu } = getThemeConfig.value;
|
||||
const { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
const layoutArr: string[] = ['defaults', 'columns'];
|
||||
if (layoutArr.includes(layout) || (layout === 'classic' && !isClassicSplitMenu)) num = '1';
|
||||
else num = '';
|
||||
@ -161,12 +160,10 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
.then(async () => {
|
||||
Session.clear(); // 清除缓存/token等
|
||||
await resetRoute(); // 删除/重置路由
|
||||
ElMessage.success(t('message.user.logOutSuccess'));
|
||||
setTimeout(() => {
|
||||
window.location.href = ''; // 去登录页
|
||||
}, 500);
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (path === 'wareHouse') {
|
||||
@ -182,16 +179,16 @@ export default defineComponent({
|
||||
// 组件大小改变
|
||||
const onComponentSizeChange = (size: string) => {
|
||||
Local.remove('themeConfig');
|
||||
getThemeConfig.value.globalComponentSize = size;
|
||||
Local.set('themeConfig', getThemeConfig.value);
|
||||
themeConfig.value.globalComponentSize = size;
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
initComponentSize();
|
||||
window.location.reload();
|
||||
};
|
||||
// 语言切换
|
||||
const onLanguageChange = (lang: string) => {
|
||||
Local.remove('themeConfig');
|
||||
getThemeConfig.value.globalI18n = lang;
|
||||
Local.set('themeConfig', getThemeConfig.value);
|
||||
themeConfig.value.globalI18n = lang;
|
||||
Local.set('themeConfig', themeConfig.value);
|
||||
proxy.$i18n.locale = lang;
|
||||
initI18n();
|
||||
other.useTitle();
|
||||
@ -239,7 +236,7 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
return {
|
||||
getUserInfos,
|
||||
userInfos,
|
||||
onLayoutSetingClick,
|
||||
onHandleCommandClick,
|
||||
onScreenfullClick,
|
||||
@ -286,16 +283,16 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep(.el-dropdown) {
|
||||
:deep(.el-dropdown) {
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
::v-deep(.el-badge) {
|
||||
:deep(.el-badge) {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
::v-deep(.el-badge__content.is-fixed) {
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbUserNews',
|
||||
setup() {
|
||||
@ -107,7 +108,7 @@ export default defineComponent({
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
::v-deep(.el-empty__description p) {
|
||||
:deep(.el-empty__description p) {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,17 +7,20 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
|
||||
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutNavBars',
|
||||
components: { BreadcrumbIndex, TagsView },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
// 是否显示 tagsView
|
||||
const setShowTagsView = computed(() => {
|
||||
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isTagsview } = themeConfig.value;
|
||||
return layout !== 'classic' && isTagsview;
|
||||
});
|
||||
return {
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutTagsViewContextmenu',
|
||||
props: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
|
||||
<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
|
||||
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
|
||||
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
||||
<li
|
||||
v-for="(v, k) in tagsViewList"
|
||||
@ -16,9 +16,9 @@
|
||||
}
|
||||
"
|
||||
>
|
||||
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon" />
|
||||
<span>{{ $t(v.meta.title) }}</span>
|
||||
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont" v-if="isActive(v)"></i>
|
||||
<SvgIcon :name="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon" class="pr5" />
|
||||
<span>{{ setTagsViewNameI18n(v) }}</span>
|
||||
<template v-if="isActive(v)">
|
||||
<SvgIcon
|
||||
name="ele-RefreshRight"
|
||||
@ -63,7 +63,11 @@ import {
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import Sortable from 'sortablejs';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { isObjectValueEqual } from '/@/utils/arrayOperation';
|
||||
import other from '/@/utils/other';
|
||||
@ -77,14 +81,16 @@ interface TagsViewState {
|
||||
x: string | number;
|
||||
y: string | number;
|
||||
};
|
||||
sortable: any;
|
||||
tagsRefsIndex: number;
|
||||
tagsViewList: any[];
|
||||
sortable: any;
|
||||
tagsViewRoutesList: any[];
|
||||
}
|
||||
interface RouteParams {
|
||||
path: string;
|
||||
url: string;
|
||||
query: object;
|
||||
params: object;
|
||||
}
|
||||
interface CurrentContextmenu {
|
||||
meta: {
|
||||
@ -105,32 +111,50 @@ export default defineComponent({
|
||||
const scrollbarRef = ref();
|
||||
const contextmenuRef = ref();
|
||||
const tagsUlRef = ref();
|
||||
const store = useStore();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
|
||||
const storesKeepALiveNames = useKeepALiveNames();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive<TagsViewState>({
|
||||
routeActive: '',
|
||||
routePath: route.path,
|
||||
dropdown: { x: '', y: '' },
|
||||
sortable: '',
|
||||
tagsRefsIndex: 0,
|
||||
tagsViewList: [],
|
||||
sortable: '',
|
||||
tagsViewRoutesList: [],
|
||||
});
|
||||
// 动态设置 tagsView 风格样式
|
||||
const setTagsStyle = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.tagsStyle;
|
||||
return themeConfig.value.tagsStyle;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
const setTagsViewNameI18n = computed(() => {
|
||||
return (v: any) => {
|
||||
return other.setTagsViewNameI18n(v);
|
||||
};
|
||||
});
|
||||
// 设置 tagsView 高亮
|
||||
const isActive = (v: RouteParams) => {
|
||||
if (getThemeConfig.value.isShareTagsView) {
|
||||
return v.path === state.routePath;
|
||||
} else {
|
||||
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
|
||||
if ((v.query && Object.keys(v.query).length) || (v.params && Object.keys(v.params).length)) {
|
||||
// 普通传参
|
||||
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
|
||||
} else {
|
||||
// 通过 name 传参,params 取值,刷新页面参数消失
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I51RS9
|
||||
return v.path === state.routePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||
@ -142,7 +166,7 @@ export default defineComponent({
|
||||
state.routeActive = await setTagsViewHighlight(route);
|
||||
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
|
||||
state.tagsViewRoutesList = tagsViewRoutes.value;
|
||||
initTagsView();
|
||||
};
|
||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
@ -154,6 +178,7 @@ export default defineComponent({
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
storesKeepALiveNames.addCachedView(v);
|
||||
}
|
||||
});
|
||||
await addTagsView(route.path, route);
|
||||
@ -175,11 +200,13 @@ export default defineComponent({
|
||||
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) return false;
|
||||
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 });
|
||||
await storesKeepALiveNames.addCachedView(findItem);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
@ -219,23 +246,31 @@ export default defineComponent({
|
||||
if (state.tagsViewList.some((v: any) => v.path === path)) return false;
|
||||
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
|
||||
}
|
||||
if (!item) return false;
|
||||
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 storesKeepALiveNames.addCachedView(item);
|
||||
await state.tagsViewList.push({ ...item });
|
||||
await addBrowserSetSession(state.tagsViewList);
|
||||
});
|
||||
};
|
||||
// 2、刷新当前 tagsView:
|
||||
const refreshCurrentTagsView = (fullPath: string) => {
|
||||
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
|
||||
const refreshCurrentTagsView = async (fullPath: string) => {
|
||||
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === fullPath : v.url === fullPath));
|
||||
if (item != null) {
|
||||
await storesKeepALiveNames.delCachedView(item);
|
||||
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
|
||||
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
|
||||
}
|
||||
};
|
||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||
const closeCurrentTagsView = (path: string) => {
|
||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
||||
if (!v.meta.isAffix) {
|
||||
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
|
||||
storesKeepALiveNames.delCachedView(v);
|
||||
state.tagsViewList.splice(k, 1);
|
||||
setTimeout(() => {
|
||||
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
|
||||
@ -269,29 +304,40 @@ export default defineComponent({
|
||||
};
|
||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeOtherTagsView = (path: string) => {
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
});
|
||||
addTagsView(path, route);
|
||||
if (Session.get('tagsViewList')) {
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
storesKeepALiveNames.delOthersCachedViews(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
}
|
||||
});
|
||||
addTagsView(path, route);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
const closeAllTagsView = () => {
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
state.tagsViewList.push({ ...v });
|
||||
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
if (Session.get('tagsViewList')) {
|
||||
storesKeepALiveNames.delAllCachedViews();
|
||||
state.tagsViewList = [];
|
||||
Session.get('tagsViewList').map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) {
|
||||
v.url = setTagsViewHighlight(v);
|
||||
state.tagsViewList.push({ ...v });
|
||||
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
|
||||
}
|
||||
});
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
}
|
||||
};
|
||||
// 6、开启当前页面全屏
|
||||
const openCurrenFullscreen = async (path: string) => {
|
||||
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
|
||||
if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
|
||||
else await router.push({ name: item.name, query: item.query });
|
||||
store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
|
||||
stores.setCurrenFullscreen(true);
|
||||
};
|
||||
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
|
||||
// 防止 tagsView 非当前页演示时,操作异常
|
||||
@ -498,11 +544,11 @@ export default defineComponent({
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
// 取消非本页面调用监听
|
||||
proxy.mittBus.off('onCurrentContextmenuClick');
|
||||
proxy.mittBus.off('onCurrentContextmenuClick', () => {});
|
||||
// 取消监听布局配置界面开启/关闭拖拽
|
||||
proxy.mittBus.off('openOrCloseSortable');
|
||||
proxy.mittBus.off('openOrCloseSortable', () => {});
|
||||
// 取消监听布局配置开启 TagsView 共用
|
||||
proxy.mittBus.off('openShareTagsView');
|
||||
proxy.mittBus.off('openShareTagsView', () => {});
|
||||
// 取消窗口 resize 监听
|
||||
window.removeEventListener('resize', onSortableResize);
|
||||
});
|
||||
@ -512,11 +558,11 @@ export default defineComponent({
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// 初始化 vuex 中的 tagsViewRoutes 列表
|
||||
// 初始化 pinia 中的 tagsViewRoutes 列表
|
||||
getTagsViewRoutes();
|
||||
initSortable();
|
||||
});
|
||||
// 路由更新时
|
||||
// 路由更新时(组件内生命钩子)
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
state.routeActive = setTagsViewHighlight(to);
|
||||
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
|
||||
@ -524,10 +570,16 @@ export default defineComponent({
|
||||
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
|
||||
});
|
||||
// 监听路由的变化,动态赋值给 tagsView
|
||||
watch(store.state, (val) => {
|
||||
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||
getTagsViewRoutes();
|
||||
});
|
||||
watch(
|
||||
pinia.state,
|
||||
(val) => {
|
||||
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||
getTagsViewRoutes();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
isActive,
|
||||
onContextmenu,
|
||||
@ -539,6 +591,7 @@ export default defineComponent({
|
||||
onHandleScroll,
|
||||
getThemeConfig,
|
||||
setTagsStyle,
|
||||
setTagsViewNameI18n,
|
||||
refreshCurrentTagsView,
|
||||
closeCurrentTagsView,
|
||||
onCurrentContextmenuClick,
|
||||
@ -554,7 +607,7 @@ export default defineComponent({
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
::v-deep(.el-scrollbar__wrap) {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
&-ul {
|
||||
@ -573,7 +626,7 @@ export default defineComponent({
|
||||
line-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
padding: 0 15px;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
@ -584,7 +637,7 @@ export default defineComponent({
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary-light-6);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
}
|
||||
&-iconfont {
|
||||
position: relative;
|
||||
|
||||
@ -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">
|
||||
<el-menu router :default-active="defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
@ -33,8 +33,11 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import SubItem from '/@/layout/navMenu/subItem.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navMenuHorizontal',
|
||||
components: { SubItem },
|
||||
@ -46,8 +49,11 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const stores = useRoutesList();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { routesList } = storeToRefs(stores);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const state = reactive({
|
||||
defaultActive: null,
|
||||
});
|
||||
@ -69,7 +75,7 @@ export default defineComponent({
|
||||
});
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<object>) => {
|
||||
const filterRoutesFun = (arr: Array<string>) => {
|
||||
return arr
|
||||
.filter((item: any) => !item.meta.isHide)
|
||||
.map((item: any) => {
|
||||
@ -82,7 +88,7 @@ export default defineComponent({
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
||||
filterRoutesFun(routesList.value).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
@ -95,7 +101,7 @@ export default defineComponent({
|
||||
// 设置页面当前路由高亮
|
||||
const setCurrentRouterHighlight = (currentRoute: any) => {
|
||||
const { path, meta } = currentRoute;
|
||||
if (store.state.themeConfig.themeConfig.layout === 'classic') {
|
||||
if (themeConfig.value.layout === 'classic') {
|
||||
(<any>state.defaultActive) = `/${path.split('/')[1]}`;
|
||||
} else {
|
||||
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
|
||||
@ -116,7 +122,7 @@ export default defineComponent({
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
setCurrentRouterHighlight(to);
|
||||
// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
|
||||
}
|
||||
@ -135,10 +141,10 @@ export default defineComponent({
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-right: 30px;
|
||||
::v-deep(.el-scrollbar__bar.is-vertical) {
|
||||
:deep(.el-scrollbar__bar.is-vertical) {
|
||||
display: none;
|
||||
}
|
||||
::v-deep(a) {
|
||||
:deep(a) {
|
||||
width: 100%;
|
||||
}
|
||||
.el-menu.el-menu--horizontal {
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navMenuSubItem',
|
||||
props: {
|
||||
|
||||
@ -33,8 +33,10 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import SubItem from '/@/layout/navMenu/subItem.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navMenuVertical',
|
||||
components: { SubItem },
|
||||
@ -45,7 +47,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
@ -58,7 +61,7 @@ export default defineComponent({
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 菜单高亮(详情时,父级高亮)
|
||||
const setParentHighlight = (currentRoute: any) => {
|
||||
@ -69,9 +72,9 @@ export default defineComponent({
|
||||
};
|
||||
// 设置菜单的收起/展开
|
||||
watch(
|
||||
store.state.themeConfig.themeConfig,
|
||||
themeConfig.value,
|
||||
() => {
|
||||
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = getThemeConfig.value.isCollapse);
|
||||
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@ -86,7 +89,7 @@ export default defineComponent({
|
||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||
state.defaultActive = setParentHighlight(to);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
});
|
||||
return {
|
||||
menuLists,
|
||||
|
||||
@ -1,19 +1,26 @@
|
||||
<template>
|
||||
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
|
||||
<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" id="iframe" v-show="!iframeLoading"></iframe>
|
||||
<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" ref="iframeDom" v-show="!iframeLoading"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutIfameView',
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const state = reactive({
|
||||
iframeDom: null as HTMLIFrameElement | null,
|
||||
iframeLoading: true,
|
||||
iframeUrl: '',
|
||||
});
|
||||
@ -22,7 +29,7 @@ export default defineComponent({
|
||||
state.iframeUrl = <any>route.meta.isLink;
|
||||
nextTick(() => {
|
||||
state.iframeLoading = true;
|
||||
const iframe = document.getElementById('iframe');
|
||||
const iframe = state.iframeDom;
|
||||
if (!iframe) return false;
|
||||
iframe.onload = () => {
|
||||
state.iframeLoading = false;
|
||||
@ -31,12 +38,11 @@ export default defineComponent({
|
||||
};
|
||||
// 设置 iframe 的高度
|
||||
const setIframeHeight = computed(() => {
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
|
||||
if (isTagsViewCurrenFull) {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `1px`;
|
||||
} else {
|
||||
if (isTagsview) return `85px`;
|
||||
if (isTagsview) return `86px`;
|
||||
else return `51px`;
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
|
||||
import { useRoute, RouteMeta } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface LinkViewState {
|
||||
@ -26,8 +27,9 @@ interface LinkViewRouteMeta extends RouteMeta {
|
||||
export default defineComponent({
|
||||
name: 'layoutLinkView',
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const state = reactive<LinkViewState>({
|
||||
currentRouteMeta: {
|
||||
isLink: '',
|
||||
@ -36,8 +38,8 @@ export default defineComponent({
|
||||
});
|
||||
// 设置 link 的高度
|
||||
const setLinkHeight = computed(() => {
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
if (isTagsview) return `114px`;
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsview) return `115px`;
|
||||
else return `80px`;
|
||||
});
|
||||
// 监听路由的变化,设置内容
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
<div class="h100">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="setTransitionName" mode="out-in">
|
||||
<keep-alive :include="keepAliveNameList">
|
||||
<component :is="Component" :key="refreshRouterViewKey" class="w100" :style="{ minHeight }" />
|
||||
<keep-alive :include="getKeepAliveNames">
|
||||
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
@ -11,9 +11,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
|
||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface ParentViewState {
|
||||
@ -23,59 +26,61 @@ interface ParentViewState {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'layoutParentView',
|
||||
props: {
|
||||
minHeight: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const storesKeepAliveNames = useKeepALiveNames();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const state = reactive<ParentViewState>({
|
||||
refreshRouterViewKey: null,
|
||||
keepAliveNameList: [],
|
||||
});
|
||||
// 设置主界面切换动画
|
||||
const setTransitionName = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.animation;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
return themeConfig.value.animation;
|
||||
});
|
||||
// 获取组件缓存列表(name值)
|
||||
const getKeepAliveNames = computed(() => {
|
||||
return store.state.keepAliveNames.keepAliveNames;
|
||||
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
|
||||
});
|
||||
// 页面加载前,处理缓存,页面刷新时路由缓存处理
|
||||
onBeforeMount(() => {
|
||||
state.keepAliveNameList = getKeepAliveNames.value;
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
|
||||
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
|
||||
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
|
||||
state.refreshRouterViewKey = null;
|
||||
nextTick(() => {
|
||||
state.refreshRouterViewKey = fullPath;
|
||||
state.keepAliveNameList = getKeepAliveNames.value;
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
});
|
||||
});
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if (themeConfig.value.isCacheTagsView) cachedViews.value = Session.get('tagsViewList')?.map((item: any) => item.name);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('onTagsViewRefreshRouterView');
|
||||
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
|
||||
});
|
||||
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
state.refreshRouterViewKey = route.fullPath;
|
||||
state.refreshRouterViewKey = decodeURI(route.fullPath);
|
||||
}
|
||||
);
|
||||
return {
|
||||
getThemeConfig,
|
||||
getKeepAliveNames,
|
||||
setTransitionName,
|
||||
getKeepAliveNames,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createApp } from 'vue';
|
||||
import pinia from '/@/stores/index';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import { store, key } from './store';
|
||||
import { directive } from '/@/utils/directive';
|
||||
import { i18n } from '/@/i18n/index';
|
||||
import other from '/@/utils/other';
|
||||
@ -17,6 +17,6 @@ const app = createApp(App);
|
||||
directive(app);
|
||||
other.elSvg(app);
|
||||
|
||||
app.use(router).use(store, key).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).mount('#app');
|
||||
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).mount('#app');
|
||||
|
||||
app.config.globalProperties.mittBus = mitt();
|
||||
|
||||
@ -1,14 +1,23 @@
|
||||
import { store } from '/@/store/index.ts';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
|
||||
import { dynamicRoutes } from '/@/router/route';
|
||||
import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
|
||||
import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useMenuApi } from '/@/api/menu/index';
|
||||
|
||||
const menuApi = useMenuApi();
|
||||
|
||||
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
|
||||
@ -18,29 +27,73 @@ const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...lay
|
||||
|
||||
/**
|
||||
* 后端控制路由:初始化方法,防止刷新时路由丢失
|
||||
* @method NextLoading 界面 loading 动画开始执行
|
||||
* @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
|
||||
* @method store.dispatch('requestOldRoutes/setBackEndControlRoutes') 存储接口原始路由(未处理component),根据需求选择使用
|
||||
* @method NextLoading 界面 loading 动画开始执行
|
||||
* @method useUserInfo().setUserInfos() 触发初始化用户信息 pinia
|
||||
* @method useRequestOldRoutes().setRequestOldRoutes() 存储接口原始路由(未处理component),根据需求选择使用
|
||||
* @method setAddRoute 添加动态路由
|
||||
* @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
* @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');
|
||||
// 触发初始化用户信息 pinia
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await useUserInfo().setUserInfos();
|
||||
// 获取路由菜单数据
|
||||
const res = await getBackEndControlRoutes();
|
||||
// 存储接口原始路由(未处理component),根据需求选择使用
|
||||
store.dispatch('requestOldRoutes/setBackEndControlRoutes', JSON.parse(JSON.stringify(res.data)));
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
|
||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
dynamicRoutes[0].children = await backEndComponent(res.data);
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
setFilterMenuAndCacheTagsViewRoutes();
|
||||
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于左侧菜单、横向菜单的显示
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setFilterMenuAndCacheTagsViewRoutes() {
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
storesRoutesList.setRoutesList(dynamicRoutes[0].children as any);
|
||||
setCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setCacheTagsViewRoutes() {
|
||||
const storesTagsView = useTagsViewRoutes(pinia);
|
||||
storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes))[0].children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理路由格式及添加捕获所有路由或 404 Not found 路由
|
||||
* @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
* @returns 返回替换后的路由数组
|
||||
*/
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
||||
return filterRouteEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动态路由
|
||||
* @method router.addRoute
|
||||
* @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
*/
|
||||
export async function setAddRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +103,9 @@ export async function initBackEndControlRoutes() {
|
||||
*/
|
||||
export function getBackEndControlRoutes() {
|
||||
// 模拟 admin 与 test
|
||||
const auth = store.state.userInfos.userInfos.roles[0];
|
||||
const stores = useUserInfo(pinia);
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
const auth = userInfos.value.roles[0];
|
||||
// 管理员 admin
|
||||
if (auth === 'admin') return menuApi.getMenuAdmin();
|
||||
// 其它用户 test
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import { store } from '/@/store/index';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
|
||||
import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
|
||||
import pinia from '/@/stores/index';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
|
||||
|
||||
// 前端控制路由
|
||||
|
||||
/**
|
||||
* 前端控制路由:初始化方法,防止刷新时路由丢失
|
||||
* @method NextLoading 界面 loading 动画开始执行
|
||||
* @method store.dispatch('userInfos/setUserInfos') 触发初始化用户信息
|
||||
* @method useUserInfo(pinia).setUserInfos() 触发初始化用户信息 pinia
|
||||
* @method setAddRoute 添加动态路由
|
||||
* @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
*/
|
||||
@ -15,10 +23,126 @@ export async function initFrontEndControlRoutes() {
|
||||
if (window.nextLoading === undefined) NextLoading.start();
|
||||
// 无 token 停止执行下一步
|
||||
if (!Session.get('token')) return false;
|
||||
// 触发初始化用户信息
|
||||
store.dispatch('userInfos/setUserInfos');
|
||||
// 触发初始化用户信息 pinia
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await useUserInfo(pinia).setUserInfos();
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
setFilterMenuAndCacheTagsViewRoutes();
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动态路由
|
||||
* @method router.addRoute
|
||||
* @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
*/
|
||||
export async function setAddRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除/重置路由
|
||||
* @method router.removeRoute
|
||||
* @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#push
|
||||
*/
|
||||
export async function frontEndsResetRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
const routeName: any = route.name;
|
||||
router.hasRoute(routeName) && router.removeRoute(routeName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有当前用户权限标识的路由数组,进行对原路由的替换
|
||||
* @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
* @returns 返回替换后的路由数组
|
||||
*/
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower];
|
||||
return filterRouteEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户权限标识去比对路由表(未处理成多级嵌套路由)
|
||||
* @description 这里主要用于动态路由的添加,router.addRoute
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
* @param chil dynamicRoutes(/@/router/route)第一个顶级 children 的下路由集合
|
||||
* @returns 返回有当前用户权限标识的路由数组
|
||||
*/
|
||||
export function setFilterRoute(chil: any) {
|
||||
const stores = useUserInfo(pinia);
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
let filterRoute: any = [];
|
||||
chil.forEach((route: any) => {
|
||||
if (route.meta.roles) {
|
||||
route.meta.roles.forEach((metaRoles: any) => {
|
||||
userInfos.value.roles.forEach((roles: any) => {
|
||||
if (metaRoles === roles) filterRoute.push({ ...route });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return filterRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setCacheTagsViewRoutes() {
|
||||
// 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
|
||||
const stores = useUserInfo(pinia);
|
||||
const storesTagsView = useTagsViewRoutes(pinia);
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
let rolesRoutes = setFilterHasRolesMenu(dynamicRoutes, userInfos.value.roles);
|
||||
// 添加到 pinia setTagsViewRoutes 中
|
||||
storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于左侧菜单、横向菜单的显示
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setFilterMenuAndCacheTagsViewRoutes() {
|
||||
const stores = useUserInfo(pinia);
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
storesRoutesList.setRoutesList(setFilterHasRolesMenu(dynamicRoutes[0].children, userInfos.value.roles));
|
||||
setCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由 `meta.roles` 中是否包含当前登录用户权限字段
|
||||
* @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
|
||||
* @param route 当前循环时的路由项
|
||||
* @returns 返回对比后有权限的路由项
|
||||
*/
|
||||
export function hasRoles(roles: any, route: any) {
|
||||
if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role));
|
||||
else return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户权限标识去比对路由表,设置递归过滤有权限的路由
|
||||
* @param routes 当前路由 children
|
||||
* @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
|
||||
* @returns 返回有权限的路由数组 `meta.roles` 中控制
|
||||
*/
|
||||
export function setFilterHasRolesMenu(routes: any, roles: any) {
|
||||
const menu: any = [];
|
||||
routes.forEach((route: any) => {
|
||||
const item = { ...route };
|
||||
if (hasRoles(roles, item)) {
|
||||
if (item.children) item.children = setFilterHasRolesMenu(item.children, roles);
|
||||
menu.push(item);
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
}
|
||||
|
||||
@ -1,32 +1,41 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { store } from '/@/store/index.ts';
|
||||
import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { staticRoutes, dynamicRoutes } from '/@/router/route';
|
||||
import { staticRoutes } from '/@/router/route';
|
||||
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
|
||||
import { initBackEndControlRoutes } from '/@/router/backEnd';
|
||||
|
||||
/**
|
||||
* 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。
|
||||
* 2、后端控制路由时:isRequestRoutes 为 true,不需要写 roles,不需要走 setFilterRoute 方法),
|
||||
* 相关方法已拆解到对应的 `backEnd.ts` 与 `frontEnd.ts`(他们互不影响,不需要同时改 2 个文件)。
|
||||
* 特别说明:
|
||||
* 1、前端控制:路由菜单由前端去写(无菜单管理界面,有角色管理界面),角色管理中有 roles 属性,需返回到 userInfo 中。
|
||||
* 2、后端控制:路由菜单由后端返回(有菜单管理界面、有角色管理界面)
|
||||
*/
|
||||
|
||||
// 读取 `/src/stores/themeConfig.ts` 是否开启后端控制路由配置
|
||||
const storesThemeConfig = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isRequestRoutes } = themeConfig.value;
|
||||
if (isRequestRoutes) staticRoutes.splice(0, 1);
|
||||
|
||||
/**
|
||||
* 创建一个可以被 Vue 应用程序使用的路由实例
|
||||
* @method createRouter(options: RouterOptions): Router
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#createrouter
|
||||
*/
|
||||
const router = createRouter({
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: staticRoutes,
|
||||
});
|
||||
|
||||
/**
|
||||
* 定义404界面
|
||||
* @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify
|
||||
*/
|
||||
const pathMatch = {
|
||||
path: '/:path(.*)*',
|
||||
redirect: '/404',
|
||||
};
|
||||
|
||||
/**
|
||||
* 路由多级嵌套数组处理成一维数组
|
||||
* @param arr 传入路由菜单数据数组
|
||||
@ -68,126 +77,14 @@ export function formatTwoStageRoutes(arr: any) {
|
||||
// 路径:/@/layout/routerView/parent.vue
|
||||
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
|
||||
cacheList.push(v.name);
|
||||
store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
|
||||
const stores = useKeepALiveNames(pinia);
|
||||
stores.setCacheKeepAlive(cacheList);
|
||||
}
|
||||
}
|
||||
});
|
||||
return newArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setCacheTagsViewRoutes() {
|
||||
// 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
|
||||
let rolesRoutes = setFilterHasRolesMenu(dynamicRoutes, store.state.userInfos.userInfos.roles);
|
||||
// 添加到 vuex setTagsViewRoutes 中
|
||||
store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由 `meta.roles` 中是否包含当前登录用户权限字段
|
||||
* @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
|
||||
* @param route 当前循环时的路由项
|
||||
* @returns 返回对比后有权限的路由项
|
||||
*/
|
||||
export function hasRoles(roles: any, route: any) {
|
||||
if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role));
|
||||
else return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户权限标识去比对路由表,设置递归过滤有权限的路由
|
||||
* @param routes 当前路由 children
|
||||
* @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组
|
||||
* @returns 返回有权限的路由数组 `meta.roles` 中控制
|
||||
*/
|
||||
export function setFilterHasRolesMenu(routes: any, roles: any) {
|
||||
const menu: any = [];
|
||||
routes.forEach((route: any) => {
|
||||
const item = { ...route };
|
||||
if (hasRoles(roles, item)) {
|
||||
if (item.children) item.children = setFilterHasRolesMenu(item.children, roles);
|
||||
menu.push(item);
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
* @description 用于左侧菜单、横向菜单的显示
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setFilterMenuAndCacheTagsViewRoutes() {
|
||||
store.dispatch('routesList/setRoutesList', setFilterHasRolesMenu(dynamicRoutes[0].children, store.state.userInfos.userInfos.roles));
|
||||
setCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户权限标识去比对路由表(未处理成多级嵌套路由)
|
||||
* @description 这里主要用于动态路由的添加,router.addRoute
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
* @param chil dynamicRoutes(/@/router/route)第一个顶级 children 的下路由集合
|
||||
* @returns 返回有当前用户权限标识的路由数组
|
||||
*/
|
||||
export function setFilterRoute(chil: any) {
|
||||
let filterRoute: any = [];
|
||||
chil.forEach((route: any) => {
|
||||
if (route.meta.roles) {
|
||||
route.meta.roles.forEach((metaRoles: any) => {
|
||||
store.state.userInfos.userInfos.roles.forEach((roles: any) => {
|
||||
if (metaRoles === roles) filterRoute.push({ ...route });
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return filterRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有当前用户权限标识的路由数组,进行对原路由的替换
|
||||
* @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
* @returns 返回替换后的路由数组
|
||||
*/
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), { ...pathMatch }];
|
||||
return filterRouteEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动态路由
|
||||
* @method router.addRoute
|
||||
* @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
*/
|
||||
export async function setAddRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
const routeName: any = route.name;
|
||||
if (!router.hasRoute(routeName)) router.addRoute(route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除/重置路由
|
||||
* @method router.removeRoute
|
||||
* @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#push
|
||||
*/
|
||||
export async function resetRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
const routeName: any = route.name;
|
||||
router.hasRoute(routeName) && router.removeRoute(routeName);
|
||||
});
|
||||
}
|
||||
|
||||
// isRequestRoutes 为 true,则开启后端控制路由,路径:`/src/store/modules/themeConfig.ts`
|
||||
const { isRequestRoutes } = store.state.themeConfig.themeConfig;
|
||||
// 前端控制路由:初始化方法,防止刷新时路由丢失
|
||||
if (!isRequestRoutes) initFrontEndControlRoutes();
|
||||
|
||||
// 路由加载前
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.configure({ showSpinner: false });
|
||||
@ -200,19 +97,24 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (!token) {
|
||||
next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`);
|
||||
Session.clear();
|
||||
resetRoute();
|
||||
NProgress.done();
|
||||
} else if (token && to.path === '/login') {
|
||||
next('/home');
|
||||
NProgress.done();
|
||||
} else {
|
||||
if (store.state.routesList.routesList.length === 0) {
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
const { routesList } = storeToRefs(storesRoutesList);
|
||||
if (routesList.value.length === 0) {
|
||||
if (isRequestRoutes) {
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
await initBackEndControlRoutes();
|
||||
// 动态添加路由:防止非首页刷新时跳转回首页的问题
|
||||
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
|
||||
next({ ...to, replace: true });
|
||||
} else {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await initFrontEndControlRoutes();
|
||||
next({ ...to, replace: true });
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
@ -224,7 +126,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
// 路由加载后
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
NextLoading.done();
|
||||
});
|
||||
|
||||
// 导出路由
|
||||
|
||||
@ -4,11 +4,11 @@ import { RouteRecordRaw } from 'vue-router';
|
||||
* 路由meta对象参数说明
|
||||
* meta: {
|
||||
* title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化)
|
||||
* isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空`
|
||||
* isLink: 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空`
|
||||
* isHide: 是否隐藏此路由
|
||||
* isKeepAlive: 是否缓存组件状态
|
||||
* isAffix: 是否固定在 tagsView 栏上
|
||||
* isIframe: 是否内嵌窗口,,开启条件,`1、isIframe:true 2、链接地址不为空`
|
||||
* isIframe: 是否内嵌窗口,开启条件,`1、isIframe:true 2、isLink:链接地址不为空`
|
||||
* roles: 当前路由权限标识,取角色管理。控制路由显示、隐藏。超级管理员:admin 普通角色:common
|
||||
* icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx`
|
||||
* }
|
||||
@ -16,6 +16,7 @@ import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 定义动态路由
|
||||
* 前端添加路由,请在顶级节点的 `children 数组` 里添加
|
||||
* @description 未开启 isRequestRoutes 为 true 时使用(前端控制路由),开启时第一个顶级 children 的路由将被替换成接口请求回来的路由数据
|
||||
* @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm`
|
||||
* @returns 返回路由菜单数据
|
||||
@ -983,8 +984,12 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
icon: 'iconfont icon-dongtai',
|
||||
},
|
||||
},
|
||||
/**
|
||||
* tagsViewName 为要设置不同的 "tagsView 名称" 字段
|
||||
* 如若需设置不同 "tagsView 名称",tagsViewName 字段必须要有
|
||||
*/
|
||||
{
|
||||
path: '/params/dynamic/details/:t/:id',
|
||||
path: '/params/dynamic/details/:t/:id/:tagsViewName',
|
||||
name: 'paramsDynamicDetails',
|
||||
component: () => import('/@/views/params/dynamic/details.vue'),
|
||||
meta: {
|
||||
@ -1128,7 +1133,33 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 定义静态路由
|
||||
* 定义404、401界面
|
||||
* @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify
|
||||
*/
|
||||
export const notFoundAndNoPower = [
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('/@/views/error/404.vue'),
|
||||
meta: {
|
||||
title: 'message.staticRoutes.notFound',
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: 'noPower',
|
||||
component: () => import('/@/views/error/401.vue'),
|
||||
meta: {
|
||||
title: 'message.staticRoutes.noPower',
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 定义静态路由(默认路由)
|
||||
* 此路由不要动,前端添加路由的话,请在 `dynamicRoutes 数组` 中添加
|
||||
* @description 前端控制直接改 dynamicRoutes 中的路由,后端控制不需要修改,请求接口路由数据时,会覆盖 dynamicRoutes 第一个顶级 children 的内容(全屏,不包含 layout 中的路由出口)
|
||||
* @returns 返回路由菜单数据
|
||||
*/
|
||||
@ -1141,22 +1172,6 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
title: '登录',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: 'notFound',
|
||||
component: () => import('/@/views/error/404.vue'),
|
||||
meta: {
|
||||
title: 'message.staticRoutes.notFound',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: 'noPower',
|
||||
component: () => import('/@/views/error/401.vue'),
|
||||
meta: {
|
||||
title: 'message.staticRoutes.noPower',
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 提示:写在这里的为全屏界面,不建议写在这里
|
||||
* 请写在 `dynamicRoutes` 路由数组中
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { InjectionKey } from 'vue';
|
||||
import { createStore, useStore as baseUseStore, Store } from 'vuex';
|
||||
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 });
|
||||
|
||||
export function useStore() {
|
||||
return baseUseStore(key);
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
// 接口类型声明
|
||||
|
||||
// 布局配置
|
||||
export interface ThemeConfigState {
|
||||
themeConfig: {
|
||||
isDrawer: boolean;
|
||||
primary: string;
|
||||
topBar: string;
|
||||
topBarColor: string;
|
||||
isTopBarColorGradual: boolean;
|
||||
menuBar: string;
|
||||
menuBarColor: string;
|
||||
isMenuBarColorGradual: boolean;
|
||||
columnsMenuBar: string;
|
||||
columnsMenuBarColor: string;
|
||||
isColumnsMenuBarColorGradual: boolean;
|
||||
isCollapse: boolean;
|
||||
isUniqueOpened: boolean;
|
||||
isFixedHeader: boolean;
|
||||
isFixedHeaderChange: boolean;
|
||||
isClassicSplitMenu: boolean;
|
||||
isLockScreen: boolean;
|
||||
lockScreenTime: number;
|
||||
isShowLogo: boolean;
|
||||
isShowLogoChange: boolean;
|
||||
isBreadcrumb: boolean;
|
||||
isTagsview: boolean;
|
||||
isBreadcrumbIcon: boolean;
|
||||
isTagsviewIcon: boolean;
|
||||
isCacheTagsView: boolean;
|
||||
isSortableTagsView: boolean;
|
||||
isShareTagsView: boolean;
|
||||
isFooter: boolean;
|
||||
isGrayscale: boolean;
|
||||
isInvert: boolean;
|
||||
isIsDark: boolean;
|
||||
isWartermark: boolean;
|
||||
wartermarkText: string;
|
||||
tagsStyle: string;
|
||||
animation: string;
|
||||
columnsAsideStyle: string;
|
||||
columnsAsideLayout: string;
|
||||
layout: string;
|
||||
isRequestRoutes: boolean;
|
||||
globalTitle: string;
|
||||
globalViceTitle: string;
|
||||
globalI18n: string;
|
||||
globalComponentSize: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 路由列表
|
||||
export interface RoutesListState {
|
||||
routesList: object[];
|
||||
isColumnsMenuHover: Boolean;
|
||||
isColumnsNavHover: Boolean;
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
export interface KeepAliveNamesState {
|
||||
keepAliveNames: string[];
|
||||
}
|
||||
|
||||
// TagsView 路由列表
|
||||
export interface TagsViewRoutesState {
|
||||
tagsViewRoutes: object[];
|
||||
isTagsViewCurrenFull: Boolean;
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
export interface UserInfosState {
|
||||
userInfos: {
|
||||
authBtnList: string[];
|
||||
photo: string;
|
||||
roles: string[];
|
||||
time: number;
|
||||
userName: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 后端返回原始路由(未处理时)
|
||||
export interface RequestOldRoutesState {
|
||||
requestOldRoutes: object[];
|
||||
}
|
||||
|
||||
// 主接口(顶级类型声明)
|
||||
export interface RootStateTypes {
|
||||
themeConfig: ThemeConfigState;
|
||||
routesList: RoutesListState;
|
||||
keepAliveNames: KeepAliveNamesState;
|
||||
tagsViewRoutes: TagsViewRoutesState;
|
||||
userInfos: UserInfosState;
|
||||
requestOldRoutes: RequestOldRoutesState;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { KeepAliveNamesState, RootStateTypes } from '/@/store/interface/index';
|
||||
|
||||
const keepAliveNamesModule: Module<KeepAliveNamesState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
keepAliveNames: [],
|
||||
},
|
||||
mutations: {
|
||||
// 设置路由缓存(name字段)
|
||||
getCacheKeepAlive(state: any, data: Array<string>) {
|
||||
state.keepAliveNames = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置路由缓存(name字段)
|
||||
async setCacheKeepAlive({ commit }, data: Array<string>) {
|
||||
commit('getCacheKeepAlive', data);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default keepAliveNamesModule;
|
||||
@ -1,23 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { RequestOldRoutesState, RootStateTypes } from '/@/store/interface/index';
|
||||
|
||||
const requestOldRoutesModule: Module<RequestOldRoutesState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
requestOldRoutes: [],
|
||||
},
|
||||
mutations: {
|
||||
// 后端控制路由
|
||||
getBackEndControlRoutes(state: any, data: object) {
|
||||
state.requestOldRoutes = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 后端控制路由
|
||||
setBackEndControlRoutes({ commit }, routes: Array<string>) {
|
||||
commit('getBackEndControlRoutes', routes);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default requestOldRoutesModule;
|
||||
@ -1,41 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { RoutesListState, RootStateTypes } from '/@/store/interface/index';
|
||||
|
||||
const routesListModule: Module<RoutesListState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
routesList: [],
|
||||
isColumnsMenuHover: false,
|
||||
isColumnsNavHover: false,
|
||||
},
|
||||
mutations: {
|
||||
// 设置路由,菜单中使用到
|
||||
getRoutesList(state: any, data: Array<object>) {
|
||||
state.routesList = data;
|
||||
},
|
||||
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||
getColumnsMenuHover(state: any, bool: Boolean) {
|
||||
state.isColumnsMenuHover = bool;
|
||||
},
|
||||
// 设置分栏布局,鼠标是否移入移出(导航)
|
||||
getColumnsNavHover(state: any, bool: Boolean) {
|
||||
state.isColumnsNavHover = bool;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置路由,菜单中使用到
|
||||
async setRoutesList({ commit }, data: any) {
|
||||
commit('getRoutesList', data);
|
||||
},
|
||||
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||
async setColumnsMenuHover({ commit }, bool: Boolean) {
|
||||
commit('getColumnsMenuHover', bool);
|
||||
},
|
||||
// 设置分栏布局,鼠标是否移入移出(菜单)
|
||||
async setColumnsNavHover({ commit }, bool: Boolean) {
|
||||
commit('getColumnsNavHover', bool);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default routesListModule;
|
||||
@ -1,34 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
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) {
|
||||
Session.set('isTagsViewCurrenFull', bool);
|
||||
state.isTagsViewCurrenFull = bool;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置 TagsView 路由
|
||||
async setTagsViewRoutes({ commit }, data: Array<string>) {
|
||||
commit('getTagsViewRoutes', data);
|
||||
},
|
||||
// 设置卡片全屏
|
||||
setCurrenFullscreen({ commit }, bool: Boolean) {
|
||||
commit('getCurrenFullscreen', bool);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default tagsViewRoutesModule;
|
||||
@ -1,34 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { UserInfosState, RootStateTypes } from '/@/store/interface/index';
|
||||
|
||||
const userInfosModule: Module<UserInfosState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
userInfos: {
|
||||
authBtnList: [],
|
||||
photo: '',
|
||||
roles: [],
|
||||
time: 0,
|
||||
userName: '',
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
// 设置用户信息
|
||||
getUserInfos(state, data: any) {
|
||||
state.userInfos = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置用户信息
|
||||
async setUserInfos({ commit }, data: UserInfosState) {
|
||||
if (data) {
|
||||
commit('getUserInfos', data);
|
||||
} else {
|
||||
if (Session.get('userInfo')) commit('getUserInfos', Session.get('userInfo'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default userInfosModule;
|
||||
8
src/stores/index.ts
Normal file
8
src/stores/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// https://pinia.vuejs.org/
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
// 创建
|
||||
const pinia = createPinia();
|
||||
|
||||
// 导出
|
||||
export default pinia;
|
||||
90
src/stores/interface/index.ts
Normal file
90
src/stores/interface/index.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 定义接口来定义对象的类型
|
||||
* `stores` 全部类型定义在这里
|
||||
*/
|
||||
|
||||
// 用户信息
|
||||
export interface UserInfosState {
|
||||
authBtnList: string[];
|
||||
photo: string;
|
||||
roles: string[];
|
||||
time: number;
|
||||
userName: string;
|
||||
}
|
||||
export interface UserInfosStates {
|
||||
userInfos: UserInfosState;
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
export interface KeepAliveNamesState {
|
||||
keepAliveNames: string[];
|
||||
cachedViews: string[];
|
||||
}
|
||||
|
||||
// 后端返回原始路由(未处理时)
|
||||
export interface RequestOldRoutesState {
|
||||
requestOldRoutes: string[];
|
||||
}
|
||||
|
||||
// TagsView 路由列表
|
||||
export interface TagsViewRoutesState {
|
||||
tagsViewRoutes: string[];
|
||||
isTagsViewCurrenFull: Boolean;
|
||||
}
|
||||
|
||||
// 路由列表
|
||||
export interface RoutesListState {
|
||||
routesList: string[];
|
||||
isColumnsMenuHover: Boolean;
|
||||
isColumnsNavHover: Boolean;
|
||||
}
|
||||
|
||||
// 布局配置
|
||||
export interface ThemeConfigState {
|
||||
isDrawer: boolean;
|
||||
primary: string;
|
||||
topBar: string;
|
||||
topBarColor: string;
|
||||
isTopBarColorGradual: boolean;
|
||||
menuBar: string;
|
||||
menuBarColor: string;
|
||||
isMenuBarColorGradual: boolean;
|
||||
columnsMenuBar: string;
|
||||
columnsMenuBarColor: string;
|
||||
isColumnsMenuBarColorGradual: boolean;
|
||||
isCollapse: boolean;
|
||||
isUniqueOpened: boolean;
|
||||
isFixedHeader: boolean;
|
||||
isFixedHeaderChange: boolean;
|
||||
isClassicSplitMenu: boolean;
|
||||
isLockScreen: boolean;
|
||||
lockScreenTime: number;
|
||||
isShowLogo: boolean;
|
||||
isShowLogoChange: boolean;
|
||||
isBreadcrumb: boolean;
|
||||
isTagsview: boolean;
|
||||
isBreadcrumbIcon: boolean;
|
||||
isTagsviewIcon: boolean;
|
||||
isCacheTagsView: boolean;
|
||||
isSortableTagsView: boolean;
|
||||
isShareTagsView: boolean;
|
||||
isFooter: boolean;
|
||||
isGrayscale: boolean;
|
||||
isInvert: boolean;
|
||||
isIsDark: boolean;
|
||||
isWartermark: boolean;
|
||||
wartermarkText: string;
|
||||
tagsStyle: string;
|
||||
animation: string;
|
||||
columnsAsideStyle: string;
|
||||
columnsAsideLayout: string;
|
||||
layout: string;
|
||||
isRequestRoutes: boolean;
|
||||
globalTitle: string;
|
||||
globalViceTitle: string;
|
||||
globalI18n: string;
|
||||
globalComponentSize: string;
|
||||
}
|
||||
export interface ThemeConfigStates {
|
||||
themeConfig: ThemeConfigState;
|
||||
}
|
||||
37
src/stores/keepAliveNames.ts
Normal file
37
src/stores/keepAliveNames.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { KeepAliveNamesState } from './interface';
|
||||
|
||||
/**
|
||||
* 路由缓存列表
|
||||
* @methods setCacheKeepAlive 设置要缓存的路由 names(开启 Tagsview)
|
||||
* @methods addCachedView 添加要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delCachedView 删除要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delOthersCachedViews 右键菜单`关闭其它`,删除要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delAllCachedViews 右键菜单`全部关闭`,删除要缓存的路由 names(关闭 Tagsview)
|
||||
*/
|
||||
export const useKeepALiveNames = defineStore('keepALiveNames', {
|
||||
state: (): KeepAliveNamesState => ({
|
||||
keepAliveNames: [],
|
||||
cachedViews: [],
|
||||
}),
|
||||
actions: {
|
||||
async setCacheKeepAlive(data: Array<string>) {
|
||||
this.keepAliveNames = data;
|
||||
},
|
||||
async addCachedView(view: any) {
|
||||
if (this.cachedViews.includes(view.name)) return;
|
||||
if (view.meta.isKeepAlive) this.cachedViews.push(view.name);
|
||||
},
|
||||
async delCachedView(view: any) {
|
||||
const index = this.cachedViews.indexOf(view.name);
|
||||
index > -1 && this.cachedViews.splice(index, 1);
|
||||
},
|
||||
async delOthersCachedViews(view: any) {
|
||||
if (view.meta.isKeepAlive) this.cachedViews = [view.name];
|
||||
else this.cachedViews = [];
|
||||
},
|
||||
async delAllCachedViews() {
|
||||
this.cachedViews = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
17
src/stores/requestOldRoutes.ts
Normal file
17
src/stores/requestOldRoutes.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RequestOldRoutesState } from './interface';
|
||||
|
||||
/**
|
||||
* 后端返回原始路由(未处理时)
|
||||
* @methods setCacheKeepAlive 设置接口原始路由数据
|
||||
*/
|
||||
export const useRequestOldRoutes = defineStore('requestOldRoutes', {
|
||||
state: (): RequestOldRoutesState => ({
|
||||
requestOldRoutes: [],
|
||||
}),
|
||||
actions: {
|
||||
async setRequestOldRoutes(routes: Array<string>) {
|
||||
this.requestOldRoutes = routes;
|
||||
},
|
||||
},
|
||||
});
|
||||
27
src/stores/routesList.ts
Normal file
27
src/stores/routesList.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RoutesListState } from './interface';
|
||||
|
||||
/**
|
||||
* 路由列表
|
||||
* @methods setRoutesList 设置路由数据
|
||||
* @methods setColumnsMenuHover 设置分栏布局菜单鼠标移入 boolean
|
||||
* @methods setColumnsNavHover 设置分栏布局最左侧导航鼠标移入 boolean
|
||||
*/
|
||||
export const useRoutesList = defineStore('routesList', {
|
||||
state: (): RoutesListState => ({
|
||||
routesList: [],
|
||||
isColumnsMenuHover: false,
|
||||
isColumnsNavHover: false,
|
||||
}),
|
||||
actions: {
|
||||
async setRoutesList(data: Array<string>) {
|
||||
this.routesList = data;
|
||||
},
|
||||
async setColumnsMenuHover(bool: Boolean) {
|
||||
this.isColumnsMenuHover = bool;
|
||||
},
|
||||
async setColumnsNavHover(bool: Boolean) {
|
||||
this.isColumnsNavHover = bool;
|
||||
},
|
||||
},
|
||||
});
|
||||
24
src/stores/tagsViewRoutes.ts
Normal file
24
src/stores/tagsViewRoutes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { TagsViewRoutesState } from './interface';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
/**
|
||||
* TagsView 路由列表
|
||||
* @methods setTagsViewRoutes 设置 TagsView 路由列表
|
||||
* @methods setCurrenFullscreen 设置开启/关闭全屏时的 boolean 状态
|
||||
*/
|
||||
export const useTagsViewRoutes = defineStore('tagsViewRoutes', {
|
||||
state: (): TagsViewRoutesState => ({
|
||||
tagsViewRoutes: [],
|
||||
isTagsViewCurrenFull: false,
|
||||
}),
|
||||
actions: {
|
||||
async setTagsViewRoutes(data: Array<string>) {
|
||||
this.tagsViewRoutes = data;
|
||||
},
|
||||
setCurrenFullscreen(bool: Boolean) {
|
||||
Session.set('isTagsViewCurrenFull', bool);
|
||||
this.isTagsViewCurrenFull = bool;
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1,14 +1,16 @@
|
||||
import { Module } from 'vuex';
|
||||
import { ThemeConfigState, RootStateTypes } from '/@/store/interface/index';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ThemeConfigStates, ThemeConfigState } from './interface';
|
||||
|
||||
/**
|
||||
* 2020.05.28 by lyt 优化
|
||||
* 修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效
|
||||
* 哪个大佬有解决办法,欢迎pr,感谢💕!
|
||||
* 布局配置
|
||||
* 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I567R1,感谢@lanbao123
|
||||
* 2020.05.28 by lyt 优化。开发时配置不生效问题
|
||||
* 修改配置时:
|
||||
* 1、需要每次都清理 `window.localStorage` 浏览器永久缓存
|
||||
* 2、或者点击布局配置最底部 `一键恢复默认` 按钮即可看到效果
|
||||
*/
|
||||
const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
export const useThemeConfig = defineStore('themeConfig', {
|
||||
state: (): ThemeConfigStates => ({
|
||||
themeConfig: {
|
||||
// 是否开启布局配置抽屉
|
||||
isDrawer: false,
|
||||
@ -18,6 +20,8 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
*/
|
||||
// 默认 primary 主题颜色
|
||||
primary: '#409eff',
|
||||
// 是否开启深色模式
|
||||
isIsDark: false,
|
||||
|
||||
/**
|
||||
* 菜单 / 顶栏
|
||||
@ -89,8 +93,6 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
isGrayscale: false,
|
||||
// 是否开启色弱模式
|
||||
isInvert: false,
|
||||
// 是否开启深色模式
|
||||
isIsDark: false,
|
||||
// 是否开启水印
|
||||
isWartermark: false,
|
||||
// 水印文案
|
||||
@ -135,19 +137,10 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'
|
||||
globalComponentSize: 'large',
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
// 设置布局配置
|
||||
getThemeConfig(state: any, data: object) {
|
||||
state.themeConfig = data;
|
||||
},
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
// 设置布局配置
|
||||
setThemeConfig({ commit }, data: object) {
|
||||
commit('getThemeConfig', data);
|
||||
setThemeConfig(data: ThemeConfigState) {
|
||||
this.themeConfig = data;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default themeConfigModule;
|
||||
});
|
||||
72
src/stores/userInfo.ts
Normal file
72
src/stores/userInfo.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import Cookies from 'js-cookie';
|
||||
import { UserInfosStates } from './interface';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
* @methods setUserInfos 设置用户信息
|
||||
*/
|
||||
export const useUserInfo = defineStore('userInfo', {
|
||||
state: (): UserInfosStates => ({
|
||||
userInfos: {
|
||||
userName: '',
|
||||
photo: '',
|
||||
time: 0,
|
||||
roles: [],
|
||||
authBtnList: [],
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
async setUserInfos() {
|
||||
// 存储用户信息到浏览器缓存
|
||||
if (Session.get('userInfo')) {
|
||||
this.userInfos = Session.get('userInfo');
|
||||
} else {
|
||||
const userInfos: any = await this.getApiUserInfo();
|
||||
this.userInfos = userInfos;
|
||||
}
|
||||
},
|
||||
// 模拟接口数据
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
async getApiUserInfo() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// 模拟数据,请求接口时,记得删除多余代码及对应依赖的引入
|
||||
const userName = Cookies.get('userName');
|
||||
// 模拟数据
|
||||
let defaultRoles: Array<string> = [];
|
||||
let defaultAuthBtnList: Array<string> = [];
|
||||
// admin 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏
|
||||
let adminRoles: Array<string> = ['admin'];
|
||||
// admin 按钮权限标识
|
||||
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
|
||||
// test 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏
|
||||
let testRoles: Array<string> = ['common'];
|
||||
// test 按钮权限标识
|
||||
let testAuthBtnList: Array<string> = ['btn.add', 'btn.link'];
|
||||
// 不同用户模拟不同的用户权限
|
||||
if (userName === 'admin') {
|
||||
defaultRoles = adminRoles;
|
||||
defaultAuthBtnList = adminAuthBtnList;
|
||||
} else {
|
||||
defaultRoles = testRoles;
|
||||
defaultAuthBtnList = testAuthBtnList;
|
||||
}
|
||||
// 用户信息模拟数据
|
||||
const userInfos = {
|
||||
userName: userName,
|
||||
photo:
|
||||
userName === 'admin'
|
||||
? 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'
|
||||
: 'https://img2.baidu.com/it/u=2370931438,70387529&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||||
time: new Date().getTime(),
|
||||
roles: defaultRoles,
|
||||
authBtnList: defaultAuthBtnList,
|
||||
};
|
||||
resolve(userInfos);
|
||||
}, 3000);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -8,12 +8,16 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--next-color-white: #ffffff;
|
||||
--next-bg-main-color: #f8f8f8;
|
||||
--next-bg-color: #f5f5ff;
|
||||
--next-border-color-light: #f1f2f3;
|
||||
--next-color-primary-lighter: #ecf5ff;
|
||||
--next-color-success-lighter: #f0f9eb;
|
||||
--next-color-warning-lighter: #fdf6ec;
|
||||
--next-color-danger-lighter: #fef0f0;
|
||||
--next-color-dark-hover: #0000001a;
|
||||
--next-color-menu-hover: rgba(0, 0, 0, 0.1);
|
||||
--next-color-menu-hover: rgba(0, 0, 0, 0.2);
|
||||
--next-color-user-hover: rgba(0, 0, 0, 0.04);
|
||||
--next-color-seting-main: #e9eef3;
|
||||
--next-color-seting-aside: #d3dce6;
|
||||
|
||||
@ -26,6 +26,9 @@
|
||||
--next-bg-columnsMenuBarColor: var(--next-color-bar) !important;
|
||||
--next-border-color-light: var(--next-border-black) !important;
|
||||
--next-color-primary-lighter: var(--next-color-primary) !important;
|
||||
--next-color-success-lighter: var(--next-color-primary) !important;
|
||||
--next-color-warning-lighter: var(--next-color-primary) !important;
|
||||
--next-color-danger-lighter: var(--next-color-primary) !important;
|
||||
--next-bg-color: var(--next-color-primary) !important;
|
||||
--next-color-dark-hover: var(--next-color-hover) !important;
|
||||
--next-color-menu-hover: var(--next-color-hover-rgba) !important;
|
||||
@ -37,20 +40,24 @@
|
||||
// element plus
|
||||
--el-color-white: var(--next-color-disabled) !important;
|
||||
--el-text-color-primary: var(--next-color-bar) !important;
|
||||
--el-border-color-base: var(--next-border-black) !important;
|
||||
--el-border-color: var(--next-border-black) !important;
|
||||
--el-border-color-light: var(--next-border-black) !important;
|
||||
--el-text-color-regular: var(--next-text-color-regular) !important;
|
||||
--el-bg-color: var(--next-color-hover-rgba) !important;
|
||||
--el-color-success-lighter: var(--next-color-primary) !important;
|
||||
--el-color-warning-lighter: var(--next-color-primary) !important;
|
||||
--el-color-danger-lighter: var(--next-color-primary) !important;
|
||||
--el-color-primary-lighter: var(--next-color-primary) !important;
|
||||
--el-color-primary-light-9: var(--next-color-hover) !important;
|
||||
--el-text-color-disabled-base: var(--el-color-primary) !important;
|
||||
--el-border-color-lighter: var(--next-border-black) !important;
|
||||
--el-border-color-extra-light: var(--el-color-primary-light-8) !important;
|
||||
--el-text-color-regular: var(--next-text-color-regular) !important;
|
||||
--el-bg-color: var(--next-color-disabled) !important;
|
||||
--el-color-primary-light-9: var(--next-color-hover) !important;
|
||||
--el-text-color-disabled: var(--next-text-color-placeholder) !important;
|
||||
--el-text-color-disabled-base: var(--el-color-primary) !important;
|
||||
--el-text-color-placeholder: var(--next-text-color-placeholder) !important;
|
||||
--el-disabled-bg-color: var(--next-color-disabled) !important;
|
||||
--el-fill-base: var(--next-color-white) !important;
|
||||
--el-fill-colo: var(--next-color-hover-rgba) !important;
|
||||
--el-fill-color: var(--next-color-hover-rgba) !important;
|
||||
--el-fill-color-blank: var(--next-color-disabled) !important;
|
||||
--el-fill-color-light: var(--next-color-hover-rgba) !important;
|
||||
--el-bg-color-overlay: var(--el-color-primary-light-9) !important;
|
||||
--el-mask-color: rgb(42 42 42 / 80%);
|
||||
|
||||
// button
|
||||
.el-button {
|
||||
@ -216,4 +223,14 @@
|
||||
.layout-columns-aside {
|
||||
border-right: 1px solid var(--next-border-columns);
|
||||
}
|
||||
|
||||
// tagsView
|
||||
.tags-style-one {
|
||||
.is-active {
|
||||
color: var(--el-text-color-primary) !important;
|
||||
}
|
||||
.layout-navbars-tagsview-ul-li:hover {
|
||||
border-color: var(--el-border-color-lighter) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
|
||||
/* Input 输入框、InputNumber 计数器
|
||||
------------------------------- */
|
||||
.el-input {
|
||||
height: 100%;
|
||||
}
|
||||
// 菜单搜索
|
||||
.el-autocomplete-suggestion__wrap {
|
||||
max-height: 280px !important;
|
||||
@ -34,6 +37,9 @@
|
||||
|
||||
/* Alert 警告
|
||||
------------------------------- */
|
||||
.el-alert {
|
||||
border: 1px solid;
|
||||
}
|
||||
.el-alert__title {
|
||||
word-break: break-all;
|
||||
}
|
||||
@ -57,20 +63,17 @@
|
||||
border-right: none !important;
|
||||
width: 220px;
|
||||
}
|
||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
|
||||
.el-menu--collapse {
|
||||
width: 64px !important;
|
||||
.el-menu-item {
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
}
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: var(--next-bg-menuBarColor);
|
||||
}
|
||||
.el-menu.el-menu--horizontal {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.el-menu-item {
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
|
||||
.el-menu--collapse {
|
||||
width: 64px !important;
|
||||
}
|
||||
// 外部链接时
|
||||
.el-menu-item a,
|
||||
@ -87,49 +90,87 @@
|
||||
.el-sub-menu .fa {
|
||||
@include generalIcon;
|
||||
}
|
||||
// 高亮时
|
||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
@extend .el-menu-hover-bg-color;
|
||||
i,
|
||||
span {
|
||||
color: var(--el-menu-active-color) !important;
|
||||
}
|
||||
}
|
||||
.el-sub-menu.is-active.is-opened {
|
||||
.el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
}
|
||||
i,
|
||||
span {
|
||||
color: unset !important;
|
||||
}
|
||||
}
|
||||
// 鼠标 hover 时
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
.el-sub-menu.is-active .el-sub-menu__title,
|
||||
.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
|
||||
@extend .el-menu-hover-bg-color;
|
||||
}
|
||||
// 菜单收起时且时 a 链接
|
||||
.el-sub-menu.is-active.is-opened .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
}
|
||||
// 子级菜单背景颜色
|
||||
// .el-menu--inline {
|
||||
// background: var(--next-bg-menuBar-light-1);
|
||||
// }
|
||||
// 水平菜单、横向菜单折叠 a 标签
|
||||
.el-popper.is-dark a {
|
||||
color: var(--el-color-white) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
// 菜单收起时鼠标经过背景颜色/字体颜色
|
||||
.el-popper.is-light {
|
||||
// 水平菜单、横向菜单折叠背景色
|
||||
.el-popper.is-pure.is-light {
|
||||
// 水平菜单
|
||||
.el-menu--vertical {
|
||||
.el-menu {
|
||||
background: var(--next-bg-menuBar);
|
||||
background: var(--next-bg-menuBar);
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--vertical {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--next-bg-menuBarColor);
|
||||
}
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 横向菜单
|
||||
.el-menu--horizontal {
|
||||
background: var(--next-bg-topBar);
|
||||
.el-menu,
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
.el-sub-menu {
|
||||
height: 50px !important;
|
||||
line-height: 50px !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
background: var(--next-bg-topBar);
|
||||
.el-sub-menu__title {
|
||||
height: 50px !important;
|
||||
line-height: 50px !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--horizontal {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 横向菜单(经典、横向)布局
|
||||
.el-menu.el-menu--horizontal {
|
||||
border-bottom: none !important;
|
||||
width: 100% !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 50px !important;
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
.el-menu-item:not(.is-active):hover,
|
||||
.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
|
||||
color: var(--next-bg-topBarColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,8 +182,15 @@
|
||||
|
||||
/* Dropdown 下拉菜单
|
||||
------------------------------- */
|
||||
.el-dropdown-menu {
|
||||
list-style: none !important; /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
|
||||
}
|
||||
.el-dropdown-menu .el-dropdown-menu__item {
|
||||
white-space: nowrap;
|
||||
&:not(.is-disabled):hover {
|
||||
background-color: var(--el-dropdown-menuItem-hover-fill);
|
||||
color: var(--el-dropdown-menuItem-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Steps 步骤条
|
||||
@ -187,13 +235,20 @@
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
/* Table 表格 element plus 2.2.0 版本
|
||||
------------------------------- */
|
||||
.el-table {
|
||||
.el-button.is-text {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* scrollbar
|
||||
------------------------------- */
|
||||
.el-scrollbar__bar {
|
||||
z-index: 4;
|
||||
}
|
||||
.el-scrollbar__wrap {
|
||||
overflow-x: hidden !important;
|
||||
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
|
||||
}
|
||||
.el-select-dropdown .el-scrollbar__wrap {
|
||||
@ -216,7 +271,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid var(--el-border-color-base);
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.el-drawer__body {
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
.icon-selector-warp-row {
|
||||
height: 230px;
|
||||
overflow: hidden;
|
||||
border-top: var(--el-border-base);
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
.el-row {
|
||||
padding: 15px;
|
||||
}
|
||||
@ -35,7 +35,7 @@
|
||||
}
|
||||
.icon-selector-warp-item {
|
||||
display: flex;
|
||||
border: var(--el-border-base);
|
||||
border: 1px solid var(--el-border-color);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
@ -48,7 +48,7 @@
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-6);
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
.icon-selector-warp-item-value {
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
@ -58,7 +58,7 @@
|
||||
}
|
||||
.icon-selector-active {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-6);
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
.icon-selector-warp-item-value {
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
|
||||
@ -33,3 +33,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) {
|
||||
.error {
|
||||
.error-flex {
|
||||
padding: 0 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 文本不换行
|
||||
|
||||
@ -1,28 +1,36 @@
|
||||
/* wangeditor富文本编辑器
|
||||
------------------------------- */
|
||||
.w-e-toolbar {
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
.w-e-text-container {
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-top: none !important;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
z-index: 1 !important;
|
||||
.editor-container {
|
||||
z-index: 9999;
|
||||
.w-e-toolbar {
|
||||
border: 1px solid var(--el-border-color-light, #ebeef5) !important;
|
||||
border-bottom: 1px solid var(--el-border-color-light, #ebeef5) !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
.w-e-text-container {
|
||||
border: 1px solid var(--el-border-color-light, #ebeef5) !important;
|
||||
border-top: none !important;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* web端自定义截屏
|
||||
------------------------------- */
|
||||
#screenShotContainer {
|
||||
z-index: 9998 !important;
|
||||
}
|
||||
#toolPanel {
|
||||
height: 42px !important;
|
||||
}
|
||||
#optionPanel {
|
||||
height: 37px !important;
|
||||
[data-theme='dark'] {
|
||||
// textarea - css vars
|
||||
--w-e-textarea-bg-color: var(--el-color-white) !important;
|
||||
--w-e-textarea-color: var(--el-text-color-primary) !important;
|
||||
|
||||
// toolbar - css vars
|
||||
--w-e-toolbar-color: var(--el-text-color-primary) !important;
|
||||
--w-e-toolbar-bg-color: var(--el-color-white) !important;
|
||||
--w-e-toolbar-active-color: var(--el-text-color-primary) !important;
|
||||
--w-e-toolbar-active-bg-color: var(--next-color-menu-hover) !important;
|
||||
--w-e-toolbar-border-color: var(--el-border-color-light, #ebeef5) !important;
|
||||
|
||||
// modal - css vars
|
||||
--w-e-modal-button-bg-color: var(--el-color-primary) !important;
|
||||
--w-e-modal-button-border-color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
/**
|
||||
* 判断两数组是否相同
|
||||
* 判断两数组字符串是否相同(用于按钮权限验证),数组字符串中存在相同时会自动去重(按钮权限标识不会重复)
|
||||
* @param news 新数据
|
||||
* @param old 源数据
|
||||
* @returns 两数组相同返回 `true`,反之则反
|
||||
*/
|
||||
export function judementSameArr(news: unknown[] | string[], old: string[]): boolean {
|
||||
export function judementSameArr(newArr: unknown[] | string[], oldArr: string[]): boolean {
|
||||
const news = removeDuplicate(newArr);
|
||||
const olds = removeDuplicate(oldArr);
|
||||
let count = 0;
|
||||
const leng = old.length;
|
||||
for (let i in old) {
|
||||
const leng = olds.length;
|
||||
for (let i in olds) {
|
||||
for (let j in news) {
|
||||
if (old[i] === news[j]) count++;
|
||||
if (olds[i] === news[j]) count++;
|
||||
}
|
||||
}
|
||||
return count === leng ? true : false;
|
||||
@ -39,3 +41,26 @@ export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组、数组对象去重
|
||||
* @param arr 数组内容
|
||||
* @param attr 需要去重的键值(数组对象)
|
||||
* @returns
|
||||
*/
|
||||
export function removeDuplicate(arr: any, attr?: string) {
|
||||
if (!arr && !arr.length) {
|
||||
return arr;
|
||||
} else {
|
||||
if (attr) {
|
||||
const obj: any = {};
|
||||
const newArr = arr.reduce((cur: any, item: any) => {
|
||||
obj[item[attr]] ? '' : (obj[item[attr]] = true && item[attr] && cur.push(item));
|
||||
return cur;
|
||||
}, []);
|
||||
return newArr;
|
||||
} else {
|
||||
return Array.from(new Set([...arr]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { App } from 'vue';
|
||||
import { store } from '/@/store/index.ts';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
/**
|
||||
@ -12,14 +12,16 @@ export function authDirective(app: App) {
|
||||
// 单个权限验证(v-auth="xxx")
|
||||
app.directive('auth', {
|
||||
mounted(el, binding) {
|
||||
if (!store.state.userInfos.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
|
||||
const stores = useUserInfo();
|
||||
if (!stores.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: string) => {
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
binding.value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@ -30,7 +32,8 @@ export function authDirective(app: App) {
|
||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||
app.directive('auth-all', {
|
||||
mounted(el, binding) {
|
||||
const flag = judementSameArr(binding.value, store.state.userInfos.userInfos.authBtnList);
|
||||
const stores = useUserInfo();
|
||||
const flag = judementSameArr(binding.value, stores.userInfos.authBtnList);
|
||||
if (!flag) el.parentNode.removeChild(el);
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { store } from '/@/store/index.ts';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
|
||||
/**
|
||||
@ -7,7 +7,8 @@ import { judementSameArr } from '/@/utils/arrayOperation';
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function auth(value: string): boolean {
|
||||
return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === value);
|
||||
const stores = useUserInfo();
|
||||
return stores.userInfos.authBtnList.some((v: string) => v === value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -17,7 +18,8 @@ export function auth(value: string): boolean {
|
||||
*/
|
||||
export function auths(value: Array<string>): boolean {
|
||||
let flag = false;
|
||||
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
|
||||
const stores = useUserInfo();
|
||||
stores.userInfos.authBtnList.map((val: string) => {
|
||||
value.map((v: string) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
@ -31,5 +33,6 @@ export function auths(value: Array<string>): boolean {
|
||||
* @returns 有权限,返回 `true`,反之则反
|
||||
*/
|
||||
export function authAll(value: Array<string>): boolean {
|
||||
return judementSameArr(value, store.state.userInfos.userInfos.authBtnList);
|
||||
const stores = useUserInfo();
|
||||
return judementSameArr(value, stores.userInfos.authBtnList);
|
||||
}
|
||||
|
||||
@ -14,18 +14,18 @@ export const NextLoading = {
|
||||
div.setAttribute('class', 'loading-next');
|
||||
const htmls = `
|
||||
<div class="loading-next-box">
|
||||
<div class="loading-next-box-warp">
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-warp">
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
<div class="loading-next-box-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
div.innerHTML = htmls;
|
||||
bodys.insertBefore(div, bodys.childNodes[0]);
|
||||
|
||||
@ -2,7 +2,9 @@ import { nextTick } from 'vue';
|
||||
import type { App } from 'vue';
|
||||
import * as svg from '@element-plus/icons-vue';
|
||||
import router from '/@/router/index';
|
||||
import { store } from '/@/store/index';
|
||||
import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { i18n } from '/@/i18n/index';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import SvgIcon from '/@/components/svgIcon/index.vue';
|
||||
@ -25,16 +27,45 @@ export function elSvg(app: App) {
|
||||
* @method const title = useTitle(); ==> title()
|
||||
*/
|
||||
export function useTitle() {
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
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));
|
||||
let globalTitle: string = themeConfig.value.globalTitle;
|
||||
const { path, meta } = router.currentRoute.value;
|
||||
if (path === '/login') {
|
||||
webTitle = <any>meta.title;
|
||||
} else {
|
||||
webTitle = setTagsViewNameI18n(router.currentRoute.value);
|
||||
}
|
||||
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
* @param params 路由 query、params 中的 tagsViewName
|
||||
* @returns 返回当前 tagsViewName 名称
|
||||
*/
|
||||
export function setTagsViewNameI18n(item: any) {
|
||||
let tagsViewName: any = '';
|
||||
const { query, params, meta } = item;
|
||||
if (query?.tagsViewName || params?.tagsViewName) {
|
||||
if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/(zh-cn|en|zh-tw)\//.test(params?.tagsViewName)) {
|
||||
// 国际化
|
||||
const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
|
||||
tagsViewName = urlTagsParams[i18n.global.locale];
|
||||
} else {
|
||||
// 非国际化
|
||||
tagsViewName = query?.tagsViewName || params?.tagsViewName;
|
||||
}
|
||||
} else {
|
||||
// 非自定义 tagsView 名称
|
||||
tagsViewName = i18n.global.t(<any>meta.title);
|
||||
}
|
||||
return tagsViewName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片懒加载
|
||||
* @param el dom 目标元素
|
||||
@ -63,7 +94,11 @@ export const lazyImg = (el: any, arr: any) => {
|
||||
* 全局组件大小
|
||||
* @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
|
||||
*/
|
||||
export const globalComponentSize: string = Local.get('themeConfig')?.globalComponentSize || store.state.themeConfig.themeConfig?.globalComponentSize;
|
||||
export const globalComponentSize = (): string => {
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* 对象深克隆
|
||||
@ -78,7 +113,7 @@ export function deepClone(obj: any) {
|
||||
newObj = {};
|
||||
}
|
||||
for (let attr in obj) {
|
||||
if (typeof obj[attr] === 'object') {
|
||||
if (obj[attr] && typeof obj[attr] === 'object') {
|
||||
newObj[attr] = deepClone(obj[attr]);
|
||||
} else {
|
||||
newObj[attr] = obj[attr];
|
||||
@ -102,14 +137,37 @@ export function isMobile() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组对象中所有属性是否为空,为空则删除当前行对象
|
||||
* @description @感谢大黄
|
||||
* @param list 数组对象
|
||||
* @returns 删除空值后的数组对象
|
||||
*/
|
||||
export function handleEmpty(list: any) {
|
||||
const arr = [];
|
||||
for (const i in list) {
|
||||
const d = [];
|
||||
for (const j in list[i]) {
|
||||
d.push(list[i][j]);
|
||||
}
|
||||
const leng = d.filter((item) => item === '').length;
|
||||
if (leng !== d.length) {
|
||||
arr.push(list[i]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一批量导出
|
||||
* @method elSvg 导出全局注册 element plus svg 图标
|
||||
* @method useTitle 设置浏览器标题国际化
|
||||
* @method setTagsViewNameI18n 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
* @method lazyImg 图片懒加载
|
||||
* @method globalComponentSize element plus 全局组件大小
|
||||
* @method globalComponentSize() element plus 全局组件大小
|
||||
* @method deepClone 对象深克隆
|
||||
* @method isMobile 判断是否是移动端
|
||||
* @method handleEmpty 判断数组对象中所有属性是否为空,为空则删除当前行对象
|
||||
*/
|
||||
const other = {
|
||||
elSvg: (app: App) => {
|
||||
@ -118,16 +176,24 @@ const other = {
|
||||
useTitle: () => {
|
||||
useTitle();
|
||||
},
|
||||
setTagsViewNameI18n(route: any) {
|
||||
return setTagsViewNameI18n(route);
|
||||
},
|
||||
lazyImg: (el: any, arr: any) => {
|
||||
lazyImg(el, arr);
|
||||
},
|
||||
globalComponentSize,
|
||||
globalComponentSize: () => {
|
||||
return globalComponentSize();
|
||||
},
|
||||
deepClone: (obj: any) => {
|
||||
deepClone(obj);
|
||||
return deepClone(obj);
|
||||
},
|
||||
isMobile: () => {
|
||||
return isMobile();
|
||||
},
|
||||
handleEmpty: (list: any) => {
|
||||
return handleEmpty(list);
|
||||
},
|
||||
};
|
||||
|
||||
// 统一批量导出
|
||||
|
||||
@ -14,7 +14,7 @@ service.interceptors.request.use(
|
||||
(config) => {
|
||||
// 在发送请求之前做些什么 token
|
||||
if (Session.get('token')) {
|
||||
config.headers.common['Authorization'] = `${Session.get('token')}`;
|
||||
(<any>config.headers).common['Authorization'] = `${Session.get('token')}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
/**
|
||||
* window.localStorage 浏览器永久缓存
|
||||
* @method set 设置永久缓存
|
||||
@ -35,19 +37,23 @@ export const Local = {
|
||||
export const Session = {
|
||||
// 设置临时缓存
|
||||
set(key: string, val: any) {
|
||||
if (key === 'token') return Cookies.set(key, val);
|
||||
window.sessionStorage.setItem(key, JSON.stringify(val));
|
||||
},
|
||||
// 获取临时缓存
|
||||
get(key: string) {
|
||||
if (key === 'token') return Cookies.get(key);
|
||||
let json: any = window.sessionStorage.getItem(key);
|
||||
return JSON.parse(json);
|
||||
},
|
||||
// 移除临时缓存
|
||||
remove(key: string) {
|
||||
if (key === 'token') return Cookies.remove(key);
|
||||
window.sessionStorage.removeItem(key);
|
||||
},
|
||||
// 移除全部临时缓存
|
||||
clear() {
|
||||
Cookies.remove('token');
|
||||
window.sessionStorage.clear();
|
||||
},
|
||||
};
|
||||
|
||||
@ -190,7 +190,7 @@ export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾
|
||||
*/
|
||||
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;
|
||||
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0|1,5-9]))\d{8}$/.test(val)) return false;
|
||||
// true: 手机号码正确
|
||||
else return true;
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
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'];
|
||||
envList.forEach((e) => {
|
||||
dotenv.config({ path: e });
|
||||
});
|
||||
for (const envName of Object.keys(process.env)) {
|
||||
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);
|
||||
if (envName === 'VITE_OPEN') realName = Boolean(realName);
|
||||
ret[envName] = realName;
|
||||
process.env[envName] = realName;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -412,15 +412,15 @@
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
margin-left: 10px;
|
||||
::v-deep(.el-progress__text) {
|
||||
:deep(.el-progress__text) {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 12px !important;
|
||||
text-align: right;
|
||||
}
|
||||
::v-deep(.el-progress-bar__outer) {
|
||||
:deep(.el-progress-bar__outer) {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
::v-deep(.el-progress-bar) {
|
||||
:deep(.el-progress-bar) {
|
||||
margin-right: -22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, onBeforeMount, onUnmounted, defineComponent } from 'vue';
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'chartHead',
|
||||
setup() {
|
||||
@ -66,9 +67,9 @@ export default defineComponent({
|
||||
background-image: -webkit-linear-gradient(
|
||||
left,
|
||||
var(--el-color-primary),
|
||||
var(--el-color-primary-light-1) 25%,
|
||||
var(--el-color-primary-light-3) 25%,
|
||||
var(--el-color-primary) 50%,
|
||||
var(--el-color-primary-light-1) 75%,
|
||||
var(--el-color-primary-light-3) 75%,
|
||||
var(--el-color-primary)
|
||||
);
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
@ -203,17 +203,23 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, onMounted, getCurrentInstance, watch, nextTick, onActivated, defineComponent } from 'vue';
|
||||
import { useStore } from '/@/store/index';
|
||||
import ChartHead from '/@/views/chart/head.vue';
|
||||
import * as echarts from 'echarts';
|
||||
import 'echarts-wordcloud';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { skyList, dBtnList, chartData4List } from '/@/views/chart/chart';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'chartIndex',
|
||||
components: { ChartHead },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const state = reactive({
|
||||
skyList,
|
||||
dBtnList,
|
||||
@ -222,9 +228,8 @@ export default defineComponent({
|
||||
});
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
|
||||
if (isTagsViewCurrenFull) {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
@ -469,7 +474,7 @@ export default defineComponent({
|
||||
});
|
||||
// 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
|
||||
watch(
|
||||
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
|
||||
() => isTagsViewCurrenFull.value,
|
||||
() => {
|
||||
initEchartsResizeFun();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="error">
|
||||
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
|
||||
<div class="error-flex">
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
@ -12,26 +12,48 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/error/401.png" />
|
||||
<img
|
||||
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
export default defineComponent({
|
||||
name: '401',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const onSetAuth = () => {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
router.push('/login');
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
};
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
else return `80px`;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onSetAuth,
|
||||
initTagViewHeight,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="error">
|
||||
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
|
||||
<div class="error-flex">
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
@ -12,24 +12,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/error/404.png" />
|
||||
<img
|
||||
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
export default defineComponent({
|
||||
name: '404',
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const router = useRouter();
|
||||
const onGoHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
else return `80px`;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onGoHome,
|
||||
initTagViewHeight,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
<template>
|
||||
<div id="printRref">
|
||||
<el-card shadow="hover" header="复制剪切演示">
|
||||
<el-alert
|
||||
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="copyText(copyVal)">复制链接</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-card shadow="hover" header="复制剪切演示">
|
||||
<el-alert
|
||||
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="copyText(copyVal)">复制链接</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, onMounted, defineComponent } from 'vue';
|
||||
import commonFunction from '/@/utils/commonFunction';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funClipboard',
|
||||
setup() {
|
||||
|
||||
@ -10,11 +10,11 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :sm="6" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
|
||||
<div class="countup-card-item countup-card-item-box" :style="{ background: `var(${v.color})` }">
|
||||
<div class="countup-card-item-flex">
|
||||
<div class="countup-card-item-flex" ref="topCardItemRefs">
|
||||
<div class="countup-card-item-title pb3">{{ v.title }}</div>
|
||||
<div class="countup-card-item-title-num pb6" :id="`titleNum${k + 1}`"></div>
|
||||
<div class="countup-card-item-title-num pb6"></div>
|
||||
<div class="countup-card-item-tip pb3">{{ v.tip }}</div>
|
||||
<div class="countup-card-item-tip-num" :id="`tipNum${k + 1}`"></div>
|
||||
<div class="countup-card-item-tip-num"></div>
|
||||
</div>
|
||||
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
|
||||
</div>
|
||||
@ -39,10 +39,12 @@
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, onMounted, nextTick, defineComponent } from 'vue';
|
||||
import { CountUp } from 'countup.js';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funCountup',
|
||||
setup() {
|
||||
const state = reactive({
|
||||
topCardItemRefs: null as any,
|
||||
topCardItemList: [
|
||||
{
|
||||
title: '今日访问人数',
|
||||
@ -50,7 +52,7 @@ export default defineComponent({
|
||||
tip: '在场人数',
|
||||
tipNum: '911',
|
||||
color: '--el-color-primary',
|
||||
iconColor: '#F86C6B',
|
||||
iconColor: '#ffcb47',
|
||||
icon: 'iconfont icon-jinridaiban',
|
||||
},
|
||||
{
|
||||
@ -59,7 +61,7 @@ export default defineComponent({
|
||||
tip: '使用中',
|
||||
tipNum: '611',
|
||||
color: '--el-color-success',
|
||||
iconColor: '#92A1F4',
|
||||
iconColor: '#70cf41',
|
||||
icon: 'iconfont icon-AIshiyanshi',
|
||||
},
|
||||
{
|
||||
@ -67,8 +69,8 @@ export default defineComponent({
|
||||
titleNum: '123',
|
||||
tip: '通过人数',
|
||||
tipNum: '911',
|
||||
color: '#FEBB50',
|
||||
iconColor: '--el-color-warning',
|
||||
color: '--el-color-warning',
|
||||
iconColor: '#dfae64',
|
||||
icon: 'iconfont icon-shenqingkaiban',
|
||||
},
|
||||
{
|
||||
@ -77,7 +79,7 @@ export default defineComponent({
|
||||
tip: '销售数',
|
||||
tipNum: '911',
|
||||
color: '--el-color-danger',
|
||||
iconColor: '#1dbcd5',
|
||||
iconColor: '#e56565',
|
||||
icon: 'iconfont icon-ditu',
|
||||
},
|
||||
],
|
||||
@ -85,14 +87,10 @@ export default defineComponent({
|
||||
// 初始化数字滚动
|
||||
const initNumCountUp = () => {
|
||||
nextTick(() => {
|
||||
new CountUp('titleNum1', Math.random() * 10000).start();
|
||||
new CountUp('titleNum2', Math.random() * 10000).start();
|
||||
new CountUp('titleNum3', Math.random() * 10000).start();
|
||||
new CountUp('titleNum4', Math.random() * 10000).start();
|
||||
new CountUp('tipNum1', Math.random() * 1000).start();
|
||||
new CountUp('tipNum2', Math.random() * 1000).start();
|
||||
new CountUp('tipNum3', Math.random() * 1000).start();
|
||||
new CountUp('tipNum4', Math.random() * 1000).start();
|
||||
state.topCardItemRefs.forEach((v: HTMLDivElement) => {
|
||||
new CountUp(v.querySelector('.countup-card-item-title-num') as HTMLDivElement, Math.random() * 10000).start();
|
||||
new CountUp(v.querySelector('.countup-card-item-tip-num') as HTMLDivElement, Math.random() * 1000).start();
|
||||
});
|
||||
});
|
||||
};
|
||||
// 重置/刷新数值
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, defineComponent } from 'vue';
|
||||
import CropperDialog from '/@/components/cropper/index.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funCropper',
|
||||
components: { CropperDialog },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
|
||||
<div class="layout-view-bg-white">
|
||||
<div id="echartsMap" style="height: 100%"></div>
|
||||
<div ref="echartsMap" style="height: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -10,21 +10,27 @@
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import 'echarts/extension/bmap/bmap';
|
||||
import { useStore } from '/@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { echartsMapList, echartsMapData } from './mock';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funEchartsMap',
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const state: any = reactive({
|
||||
echartsMap: null,
|
||||
echartsMapList,
|
||||
echartsMapData,
|
||||
});
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
|
||||
if (isTagsViewCurrenFull) {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
@ -47,7 +53,7 @@ export default defineComponent({
|
||||
};
|
||||
// 初始化 echartsMap
|
||||
const initEchartsMap = () => {
|
||||
const myChart = echarts.init(<HTMLElement>document.getElementById('echartsMap'));
|
||||
const myChart = echarts.init(<HTMLElement>state.echartsMap);
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FunGridLayout',
|
||||
setup() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="printRef">
|
||||
<div ref="printRef">
|
||||
<el-card shadow="hover" header="打印演示">
|
||||
<el-alert
|
||||
title="感谢优秀的 `print-js`,项目地址:https://github.com/crabbly/Print.js。请在打印弹窗 `更多设置` 中开启 `背景图形。`"
|
||||
@ -18,14 +18,17 @@
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, defineComponent } from 'vue';
|
||||
import printJs from 'print-js';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funPrintJs',
|
||||
setup() {
|
||||
const state = reactive({});
|
||||
const state = reactive({
|
||||
printRef: null as null | HTMLDivElement,
|
||||
});
|
||||
// 打印点击
|
||||
const onPrintJs = () => {
|
||||
printJs({
|
||||
printable: 'printRef',
|
||||
printable: state.printRef,
|
||||
type: 'html',
|
||||
css: ['//at.alicdn.com/t/font_2298093_o73r8wjdhlg.css', '//unpkg.com/element-plus/dist/index.css'],
|
||||
scanStyles: false,
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, getCurrentInstance, defineComponent } from 'vue';
|
||||
import QRCode from 'qrcodejs2-fixes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funQrcode',
|
||||
setup() {
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
import 'splitpanes/dist/splitpanes.css';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funSplitpanes',
|
||||
components: { Splitpanes, Pane },
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
import { getCurrentInstance, reactive, toRefs, defineComponent } from 'vue';
|
||||
import NoticeBar from '/@/components/noticeBar/index.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funTagsView',
|
||||
components: { NoticeBar },
|
||||
@ -109,7 +110,7 @@ export default defineComponent({
|
||||
<style scoped lang="scss">
|
||||
.fun-tagsview {
|
||||
.fun-tagsview-from-item {
|
||||
::v-deep(.el-form-item__content) {
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import Editor from '/@/components/editor/index.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'funWangEditor',
|
||||
components: { Editor },
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user