43 Commits

Author SHA1 Message Date
a9089b6e63 update README.md.
Signed-off-by: lyt-Top <1105290566@qq.com>
2025-07-09 03:53:11 +00:00
181af7a2eb feat: 开发文档增加github.io链接#0
Signed-off-by: lyt-Top <1105290566@qq.com>
2025-02-13 15:26:43 +00:00
99c5361de0 update README.md.
Signed-off-by: lyt-Top <1105290566@qq.com>
2025-02-03 03:11:52 +00:00
aec7b928f5 feat: update README.md.
Signed-off-by: lyt-Top <1105290566@qq.com>
2024-09-27 09:00:43 +00:00
1091524d77 加入JFLOW驰骋工作流 2024-06-22 11:32:03 +08:00
d69fe1936b 加入JFLOW驰骋工作流 2024-06-22 11:29:45 +08:00
6f9accb3c8 update README.md.
Signed-off-by: lyt-Top <1105290566@qq.com>
2024-06-08 10:52:41 +00:00
61c2f87e93 docs: update README.md.
Signed-off-by: lyt-Top <1105290566@qq.com>
2024-04-14 10:10:15 +00:00
lyt
2adc588982 feat: 更新最新依赖、部分样式修改 2024-03-17 22:54:08 +08:00
7f4ccfd7fd docs: 5群已满,请加6群(#0)
Signed-off-by: lyt-Top <1105290566@qq.com>
2023-06-11 11:05:39 +00:00
lyt
6325151691 'admin-23.04.12:修复v2.4.33版本(#I6UW2I),分栏布局问题' 2023-04-12 12:39:13 +08:00
lyt
fae8ec55ea Merge branch 'master' of https://gitee.com/lyt-top/vue-next-admin 2023-04-11 22:05:15 +08:00
lyt
8189fec5b4 'admin-23.04.11:发布v2.4.33版本,更新内容查看CHANGELOG.md' 2023-04-11 22:04:59 +08:00
lyt
ecdb9ac58c 'admin-23.03.26:发布v2.4.33版本,更新内容查看CHANGELOG.md' 2023-04-11 22:01:28 +08:00
8de54a844b !47 cdn打包支持pnpm,消除无external的报错
* <fix>cdn打包支持pnpm,消除无external的报错
2023-04-07 13:50:10 +00:00
lyt
7544b23d7c 'admin-23.03.26:发布v2.4.32版本,更新内容查看CHANGELOG.md 2023-03-26 17:50:35 +08:00
lyt
225bce794b 'admin-23.03.26:发布v2.4.32版本,更新内容查看CHANGELOG.md 2023-03-26 17:43:05 +08:00
f92574c0f2 !46 分栏模式下,分栏菜单存在子菜单,且没有指定重定向时,只切换子菜单,不打开页面
Merge pull request !46 from 写意/master
2023-03-23 15:23:28 +00:00
a91f84e3a1 feat: 一级菜单重定向为空,分栏模式下,点击一次菜单时现在会切换子菜单列表,而不是打开空白页 2023-03-19 17:53:29 +08:00
lyt
ccca4cd355 'admin-23.03.10:发布v2.4.31版本,更新内容查看CHANGELOG.md' 2023-03-10 22:03:13 +08:00
lyt
a894768c56 'admin-23.02.27:优化分栏布局' 2023-02-27 23:22:53 +08:00
496594578a 'admin-23.02.26:群满,请加交流群5,556254895'
Signed-off-by: lyt-Top <1105290566@qq.com>
2023-02-26 02:54:01 +00:00
lyt
1cbc0f1b4e 'admin-23.02.22:发布v2.4.3版本,感谢赞助商.驰骋工作流引擎-表单引擎-低代码开发平台' 2023-02-22 22:57:15 +08:00
bf9fce206a !44 tagsViewName正则匹配错误,匹配到含en单词
Merge pull request !44 from tony星/正则
2023-02-22 06:08:17 +00:00
9195d8367f !45 fix地址栏出现false问题
Merge pull request !45 from 随心/master
2023-02-22 06:08:05 +00:00
1b3600f394 fix地址栏出现false问题 2023-01-29 09:30:23 +08:00
16823b2ef7 update src/utils/other.ts.
Signed-off-by: tony星 <7762581+tony_tong_xin@user.noreply.gitee.com>
2023-01-09 12:22:42 +00:00
lyt
9d794f2309 'admin-22.12.12:发布v2.4.21版本,具体更新内容查看CHANGELOG.md' 2022-12-12 13:00:54 +08:00
bd8ac2cc94 !42 修复 工作流无法添加新节点问题
Merge pull request !42 from beta/bugfix_workflow
2022-12-12 04:33:20 +00:00
2b9506845c 修复 工作流无法添加新节点问题
1. 修复 工作流无法添加新节点问题
2. 修复 左侧导航无法隐藏问题
2022-12-11 16:06:34 +08:00
lyt
d2d83fd70b 'admin-22.12.09:发布v2.4.2版本,具体更新内容查看CHANGELOG.md' 2022-12-09 23:55:16 +08:00
56139e72a7 !41 修复get请求传递嵌套对象或数组时无法正常编码问题
Merge pull request !41 from 随心/master
2022-12-07 10:51:38 +00:00
07e0f742d8 !40 开启TagsView缓存后,刷新后所有的路由都变成组件缓存了
Merge pull request !40 from mrjimin/master
2022-12-07 07:19:27 +00:00
c09b154a3f 修复get请求传递嵌套对象或数组时无法正常编码问题 2022-12-05 10:48:16 +08:00
6e59014357 update src/layout/routerView/parent.vue.
Signed-off-by: mrjimin <z8888788@163.com>
2022-12-03 08:22:44 +00:00
02e7c49750 update src/layout/routerView/parent.vue.
这里应该拿到的是已经设置开启组件缓存的路由,而不是全部,需要先判断item.meta.isKeepAlive

Signed-off-by: mrjimin <z8888788@163.com>
2022-12-03 07:31:01 +00:00
lyt
7b26cb21dd 'admin-22.11.30:发布v2.4.1版本,具体更新内容查看CHANGELOG.md' 2022-11-30 23:04:40 +08:00
lyt
bfecc6f6d2 Merge branch 'master' of https://gitee.com/lyt-top/vue-next-admin 2022-11-30 18:51:38 +08:00
lyt
14981044b9 'admin-22.11.30:删除v2.4.0版本不需要的依赖' 2022-11-30 18:51:14 +08:00
8ed7986a96 update src/views/error/404.vue.
Signed-off-by: lyt-Top <1105290566@qq.com>
2022-11-30 09:46:14 +00:00
852075ccfb update src/components/table/index.vue.
Signed-off-by: lyt-Top <1105290566@qq.com>
2022-11-30 09:39:44 +00:00
lyt
ba80b9bc76 'admin-22.11.30:修改v2.4.0文字说明' 2022-11-30 17:36:38 +08:00
lyt
4f8f13a722 'admin-22.11.30:修改v2.4.0文字说明' 2022-11-30 17:34:16 +08:00
103 changed files with 4315 additions and 4906 deletions

3
.env
View File

@ -4,5 +4,8 @@ VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器 # open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false VITE_OPEN = false
# 打包是否开启 cdn源文件 utils/build.ts可自行修改
VITE_OPEN_CDN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可 # public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
VITE_PUBLIC_PATH = /vue-next-admin-preview/ VITE_PUBLIC_PATH = /vue-next-admin-preview/

View File

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

View File

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

View File

@ -2,6 +2,102 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支) 🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.4.34
`2023.04.14`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 演示界面 [列表自适应](https://lyt-top.gitee.io/vue-next-admin-preview/#/pages/listAdapt) 与 [图片懒加载](https://lyt-top.gitee.io/vue-next-admin-preview/#/pages/lazyImg)
- 🐞 修复 `font-awesome.min.css` 在线 `cdn` 无法访问问题,[在线 cdn](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/build/#_3-cdn-%E5%8A%A0%E9%80%9F),感谢群友@Lauyping
## 2.4.33
`2023.04.11`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 `/make/tableDemo` 中添加打印、图片预览功能
- 🐞 修复 菜单收起时isCollapse图标不居中问题
- 🐞 修复 演示 `权限管理 -> 前端控制 -> 页面权限` 切换不生效,感谢群友@傲世盛唐
- 🐞 修复 `"typescript": "5.x"``tsconfig.json``compilerOptions.suppressImplicitAnyIndexErrors` 弃用问题 [TypeScript/issues/51909](https://github.com/microsoft/TypeScript/issues/51909)、[suppressImplicitAnyIndexErrors](https://www.typescriptlang.org/tsconfig#suppressImplicitAnyIndexErrors)
- 🎨 合并 [!47cdn 打包支持 pnpm消除无 external 的报错](https://gitee.com/lyt-top/vue-next-admin/commit/8de54a844bb54468d0bdccca158bf9bcb449f270),感谢[@yujiacheng](https://gitee.com/YujiaCheng1996)
- 🎯 优化 `layout/navBars/breadcrumb` 文件夹名称改成 `layout/navBars/topBar` 更易理解(`可全局替换`),感谢群友@傲世盛唐
- 🎯 优化 `layout/navBars/topBar/user.vue` 组件,`UserNews` 点击消息图标触发范围,改用 [element plus Popover 气泡卡片 虚拟触发方式](https://element-plus.org/zh-CN/component/popover.html#%E8%99%9A%E6%8B%9F%E8%A7%A6%E5%8F%91),防止点击消息通知背景色时不触发 `Popover` 弹出框
## 2.4.32
💔💔💔 图片不显示问题README.md、演示中使用的图片[vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images)),通过网站 [https://www.hd-r.cn/](https://www.hd-r.cn/) 转在线链接,如若侵权请联系作者 qq1105290566
`2023.03.26`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 [关于开发环境 sourceMap 的问题](https://gitee.com/lyt-top/vue-next-admin/issues/I6DNDQ),感谢[@XiaoSongJiang](https://gitee.com/XiaoSongJiang)
- 🐞 修复 打包提示 `[@vue/compiler-sfc] ::v-deep usage as a combinator has been deprecated. Use :deep(<inner-selector>) instead.`,不能使用 `:deep {}`,而应使用 `:deep() {}`
- 🎨 合并 [feat: 一级菜单重定向为空,分栏模式下,点击一次菜单时现在会切换子菜单列表,而不是打开空白页](https://gitee.com/lyt-top/vue-next-admin/commit/a91f84e3a1a86d8d303a5b46171622913d9d0737),感谢[@写意](https://gitee.com/xjj_0906)
- 🎯 优化 经典布局分割菜单只有一项子级时,收起左侧导航菜单
- 🎯 优化 watch 监听范围
- 🎯 优化 打包分包manualChunks、gzip 压缩、cdn 加速 `默认关闭 .env 中开启`(可查看文章[vue-next-admin vue3 + vite 打包 gzip 压缩、cdn 加速](https://blog.csdn.net/qq_34450741/article/details/129766676)
## 2.4.31
`2023.03.10`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 顶栏背景渐变设置不生效
- 🐞 修复 顶栏背景渐变、菜单背景渐变时,深色主题不生效
- 🐞 修复 顶栏搜索框移动端显示问题
- 🎯 优化 `main.ts`,相关 issues [#I6KNFH](https://gitee.com/lyt-top/vue-next-admin/issues/I6KNFH)、[#I6JRH6](https://gitee.com/lyt-top/vue-next-admin/issues/I6JRH6)
- 🎯 优化 菜单横向模式显示horizontal
- 🎯 优化 分栏布局,[希望分栏布局做一下优化,在没有二级菜单的时候,直接全屏展示一级菜单链接](https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H),感谢[@jiuping](https://gitee.com/jiuping)`tagsview` 点击时处理 `收起/展开` 菜单
## 2.4.3
`2023.02.22`
🚩🚩🚩 感谢 [驰骋工作流引擎-表单引擎-低代码开发平台](http://www.ccflow.org/) 赞助商的赞助。驰骋公司为社会提供流程引擎+表单引擎+低代码开发平台一体的开源软件解决方案,欢迎广大开发者前去体验!
- 🌟 更新 依赖更新最新版本
- 🎉 新增 赞助商组件(`/src/layout/sponsors`[项目目录结构查看](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/)
- 🐞 修复 [过滤筛选组件展开点击不了](https://gitee.com/lyt-top/vue-next-admin/issues/I688WG)
- 🐞 修复 [设置锁屏时间时直接白屏了不能恢复,除非删除主题配置才会重新加载](https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P),感谢[@baizunxian](https://gitee.com/xb_xiaobai)
- 🐞 修复 `分栏布局` 地址栏输入不存在的路由报错问题
- 🎨 合并 [!44 tagsViewName 正则匹配错误,匹配到含 en 单词](https://gitee.com/lyt-top/vue-next-admin/pulls/44/files),感谢[@tony 星](https://gitee.com/tony_tong_xin)
- 🎨 合并 [!45 fix 地址栏出现 false 问题](https://gitee.com/lyt-top/vue-next-admin/pulls/45),感谢[@随心](https://gitee.com/jiangqiang1996)
- 🎯 优化 `/src/utils/storage``key` 编写成 `${__NEXT_NAME__}:${key}`,防止部署多套系统到同一域名不同目录时,变量共用的问题(`__NEXT_NAME__``package.json` 中的 `name`
- 🎯 优化 watermark 单词拼写错误
## 2.4.21
`2022.12.12`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 菜单背景高亮颜色可自定义,通过 `布局配置 -> 菜单设置 -> 菜单高亮背景色` 进行设置
- 🐞 修复 `分栏布局` 二级导航菜单内容多时,无法滚动问题,感谢群友@静雨轩主人
- 🐞 修复 [!42 修复 工作流无法添加新节点问题](https://gitee.com/lyt-top/vue-next-admin/pulls/42),感谢[@beta](https://gitee.com/beta_dz)
- 🎯 优化 `/make/tableDemo` 表头很多时,无法滚动问题,感谢群友@糊涂涂涂
## 2.4.2
`2022.12.09`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 国际化自动导入文件功能,只需在 `/src/i18n/pages` 下新建文件夹定义即可
- 🎉 新增 `/make/tableDemo` 中 [搜索框展开,收缩功能,高级筛选组件 有计划做吗](https://gitee.com/lyt-top/vue-next-admin/issues/I6511L)
- 🐞 修复 [!40 开启 TagsView 缓存后,刷新后所有的路由都变成组件缓存了](https://gitee.com/lyt-top/vue-next-admin/pulls/40),感谢[@mrjimin](https://gitee.com/mrjimin)
- 🐞 修复 [!41 修复 get 请求传递嵌套对象或数组时无法正常编码问题](https://gitee.com/lyt-top/vue-next-admin/pulls/41),感谢[@随心](https://gitee.com/jiangqiang1996)
- 🐞 修复 组件 wangEditor 回显值的问题
- 🐞 修复 `/fun/echartsMap`(地理坐标/地图)、`visualizingDemo2`(数据可视化演示 2 演示报错问题
- 🎯 优化 版本升级提示
- 🎯 优化 无权限登录时增加提示信息,[BUG因前端加载路由(initFrontEndControlRoutes)中当前用户角色为一个陌生角色, 导致 router.beforeEach 会死循环 浏览器崩溃](https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO),感谢[@canroc](https://gitee.com/canroc)、[@随心](https://gitee.com/jiangqiang1996)
- 🌈 重构 `/views/system` 新增修改组件合并。[可以把新增修改组件合并成一个吧](https://gitee.com/lyt-top/vue-next-admin/issues/I64WES)
- 🌈 重构 图标选择器,[图标选择器没办法筛选,只能筛选 ali 的](https://gitee.com/lyt-top/vue-next-admin/issues/I64HZD),感谢[@随心](https://gitee.com/jiangqiang1996)
## 2.4.1
`2022.11.30`
- 🎉 新增 版本升级提示
- 🐞 修复 [先打开 F12 再登录进去,然后改变浏览器大小 js 报错](https://gitee.com/lyt-top/vue-next-admin/issues/I63ZZT),感谢[@Quber](https://gitee.com/quber)
## 2.4.0 ## 2.4.0
`2022.11.29` `2022.11.29`
@ -75,7 +171,7 @@
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题 - 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts) - 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白 - 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue` - 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/topBar/user.vue`
- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题 - 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题
- 🎯 优化 [如果 url 中有中文等特殊字符,第一次切换该 tab 时 keep-alive 失效#I55JS7](https://gitee.com/lyt-top/vue-next-admin/issues/I55JS7),感谢[yuyong1566](https://gitee.com/yuyong1566) - 🎯 优化 [如果 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) - 🎯 优化 [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)

View File

@ -1,33 +1,40 @@
<div align="center"> <div align="center">
<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"> <img src="https://i.hd-r.cn/6ce52e5724fae609444b5b48bdc4accb.png">
<p align="center"> <p align="center">
<a href="https://v3.vuejs.org/" target="_blank"> <a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue"> <img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
</a> </a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank"> <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus"> <img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus">
</a> </a>
<a href="https://www.tslang.cn/" target="_blank"> <a href="https://www.tslang.cn/" target="_blank">
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript"> <img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a> </a>
<a href="https://vitejs.dev/" target="_blank"> <a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite"> <img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a> </a>
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank"> <a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license"> <img src="https://img.shields.io/badge/license-MIT-success" alt="license">
</a> </a>
</p> </p>
<p>&nbsp;</p> <p>&nbsp;</p>
</div> </div>
#### 💝 长期赞助商
<a href="http://www.ccflow.org/" target="_blank">
<img src="./src/assets/ccflowRightNextAdmin.png" width="50%" height="70px">
</a>
#### 🌈 介绍 #### 🌈 介绍
基于 vue3.x + CompositionAPI setup 语法糖 + typescript + vite + element plus + vue-router-next + pinia 技术适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。 基于 vue3.x + CompositionAPI setup 语法糖 + typescript + vite + element plus + vue-router-next + pinia 技术适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### ⛱️ 线上预览 #### ⛱️ 线上预览
- vue3.x 版本预览vue-next-admin<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-next-admin-preview/#/login</a> - vue3.x 版本预览vue-next-admin<a href="http://vuenextadmin.ccfast.cc/ " target="_blank">http://vuenextadmin.ccfast.cc/ </a>
- vue2.x 版本预览vue-prev-admin<a href="https://lyt-top.gitee.io/vue-prev-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-prev-admin-preview/#/login</a> - vue2.x 版本预览vue-prev-admin<a href="https://lyt-top.gitee.io/vue-prev-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-prev-admin-preview/#/login</a>
- vue3.x + uni-app 商城 H5vue-next-admin-shop<a href="https://lyt-top.gitee.io/vue-next-admin-shop-preview" target="_blank">https://lyt-top.gitee.io/vue-next-admin-shop-preview</a>
#### 💒 代码仓库 #### 💒 代码仓库
@ -43,13 +50,15 @@
| Edge | Firefox | Chrome | Safari | | Edge | Firefox | Chrome | Safari |
| --------- | ------------ | ----------- | ----------- | | --------- | ------------ | ----------- | ----------- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 72 | Safari ≥ 12 | | Edge ≥ 88 | Firefox ≥ 78 | Chrome ≥ 87 | Safari ≥ 13 |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。 > 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
#### ⚡ 使用说明 #### ⚡ 使用说明
建议使用 cnpm因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 12xx.xx.x</a> 建议使用 cnpm因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本 > 14.18+/16+</a>
> Vite 不再支持 Node 12 / 13 / 15因为上述版本已经进入了 EOL 阶段。现在你必须使用 Node 14.18+ / 16+ 版本。
```bash ```bash
# 克隆项目 # 克隆项目
@ -70,32 +79,21 @@ cnpm run build
#### 📚 开发文档 #### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a> - 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">https://lyt-top.gitee.io/vue-next-admin-doc-preview</a>
- 查看开发文档:<a href="https://lyt-top.github.io/vue-next-admin-doc-preview/" target="_blank">https://lyt-top.github.io/vue-next-admin-doc-preview/</a>
#### 💯 学习交流加 QQ 群 #### 💯 学习交流加 QQ 群
> 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。 > 群已满,请加以下群号,感谢老哥们支持!
- 1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a> 群号931596000
- 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"> 其它交流群请查看文档首页 [vueNextAdmin 解疑问](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)
<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://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://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://gitee.com/zuohuaijun/Admin.NET">@zuohuaijun Admin.NET</a>
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a> - <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
- <a target="_blank" href="https://toscode.gitee.com/GionConnection/gopro_free">@甜蜜蜜 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/GionConnection/niupi-free">@甜蜜蜜 NiuPi 平台</a>
@ -116,7 +114,6 @@ cnpm run build
- <a href="https://github.com/vuejs/pinia" target="_blank">pinia</a> - <a href="https://github.com/vuejs/pinia" target="_blank">pinia</a>
- <a href="https://github.com/apache/echarts" target="_blank">echarts</a> - <a href="https://github.com/apache/echarts" target="_blank">echarts</a>
- <a href="https://github.com/axios/axios" target="_blank">axios</a> - <a href="https://github.com/axios/axios" target="_blank">axios</a>
- <a href="https://github.com/zenorocha/clipboard.js" target="_blank">clipboard</a>
- <a href="https://github.com/inorganik/countUp.js" target="_blank">countUp</a> - <a href="https://github.com/inorganik/countUp.js" target="_blank">countUp</a>
- <a href="https://github.com/developit/mitt" target="_blank">mitt</a> - <a href="https://github.com/developit/mitt" target="_blank">mitt</a>
- <a href="https://github.com/rstacruz/nprogress" target="_blank">nprogress</a> - <a href="https://github.com/rstacruz/nprogress" target="_blank">nprogress</a>
@ -127,12 +124,19 @@ cnpm run build
- <a href="https://github.com/vitejs/vite" target="_blank">vite</a> - <a href="https://github.com/vitejs/vite" target="_blank">vite</a>
- <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a> - <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a>
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a> - <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs</a>
- <a href="https://github.com/crabbly/Print.js" target="_blank">print-js</a> - <a href="https://github.com/crabbly/Print.js" target="_blank">print-js</a>
- <a href="https://github.com/jbaysolutions/vue-grid-layout" target="_blank">vue-grid-layout</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/antoniandre/splitpanes" target="_blank">splitpanes</a>
- <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a> - <a href="https://github.com/jsplumb/jsplumb" target="_blank">jsplumb</a>
- <a href="https://github.com/hxj9102/table2excel" target="_blank">js-table2excel</a> - <a href="https://github.com/hxj9102/table2excel" target="_blank">js-table2excel</a>
- <a href="https://github.com/mmf-fe/vite-plugin-cdn-import" target="_blank">vite-plugin-cdn-import</a>
- <a href="https://github.com/js-cookie/js-cookie" target="_blank">js-cookie</a>
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs2-fixes</a>
- <a href="https://github.com/ljharb/qs" target="_blank">qs</a>
- <a href="https://github.com/JamieCurnow/vue-clipboard3" target="_blank">vue-clipboard3</a>
- <a href="https://github.com/intlify/vue-i18n-next" target="_blank">vue-i18n</a>
- <a href="https://github.com/vbenjs/vite-plugin-compression" target="_blank">vite-plugin-compression</a>
- <a href="https://github.com/chenxch/vite-plugin-vue-setup-extend-plus" target="_blank">vite-plugin-vue-setup-extend-plus</a>
#### 💕 特别感谢 #### 💕 特别感谢

5660
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "vue-next-admin", "name": "vue-next-admin",
"version": "2.4.0", "version": "2.4.33",
"description": "vue3 vite next admin template", "description": "vue3 vite next admin template",
"author": "lyt_20201208", "author": "lyt_20201208",
"license": "MIT", "license": "MIT",
@ -10,51 +10,53 @@
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/" "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.3.1",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.2.0", "axios": "^1.6.8",
"countup.js": "^2.3.2", "countup.js": "^2.8.0",
"cropperjs": "^1.5.13", "cropperjs": "^1.6.1",
"echarts": "^5.4.0", "echarts": "^5.5.0",
"echarts-gl": "^2.0.9", "echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "^2.2.25", "element-plus": "^2.6.1",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.5",
"js-table2excel": "^1.0.3", "js-table2excel": "^1.1.2",
"jsplumb": "^2.15.6", "jsplumb": "^2.15.6",
"mitt": "^3.0.0", "mitt": "^3.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.27", "pinia": "^2.1.7",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2", "qrcodejs2-fixes": "^0.0.2",
"qs": "^6.12.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.2",
"splitpanes": "^3.1.5", "splitpanes": "^3.1.5",
"vue": "^3.2.45", "vue": "^3.4.21",
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",
"vue-demi": "^0.14.7",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.10.2",
"vue-router": "^4.1.6" "vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.11.9", "@types/node": "^20.11.28",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.0", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-vue": "^3.2.0", "@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "^3.2.45", "@vue/compiler-sfc": "^3.4.21",
"dotenv": "^16.0.3", "eslint": "^8.57.0",
"eslint": "^8.28.0", "eslint-plugin-vue": "^9.23.0",
"eslint-plugin-vue": "^9.8.0", "prettier": "^3.2.5",
"prettier": "^2.8.0", "sass": "^1.72.0",
"sass": "^1.56.1", "typescript": "^5.4.2",
"sass-loader": "^13.2.0", "vite": "^5.1.6",
"typescript": "^4.9.3", "vite-plugin-cdn-import": "^0.3.5",
"vite": "^3.2.4", "vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-setup-extend": "^0.4.0", "vite-plugin-vue-setup-extend-plus": "^0.1.0",
"vue-eslint-parser": "^9.1.0" "vue-eslint-parser": "^9.4.2"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
@ -65,8 +67,8 @@
"url": "https://gitee.com/lyt-top/vue-next-admin/issues" "url": "https://gitee.com/lyt-top/vue-next-admin/issues"
}, },
"engines": { "engines": {
"node": ">=12.0.0", "node": ">=16.0.0",
"npm": ">= 6.0.0" "npm": ">= 7.0.0"
}, },
"keywords": [ "keywords": [
"vue", "vue",

View File

@ -1,9 +1,11 @@
<template> <template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n"> <el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" /> <router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" /> <LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" /> <Setings ref="setingsRef" v-show="setLockScreen" />
<CloseFull v-if="!themeConfig.isLockScreen" /> <CloseFull v-if="!themeConfig.isLockScreen" />
<Upgrade v-if="getVersion" />
<Sponsors />
</el-config-provider> </el-config-provider>
</template> </template>
@ -21,8 +23,10 @@ import setIntroduction from '/@/utils/setIconfont';
// 引入组件 // 引入组件
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')); const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')); const Setings = defineAsyncComponent(() => import('/@/layout/navBars/topBar/setings.vue'));
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')); const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/topBar/closeFull.vue'));
const Upgrade = defineAsyncComponent(() => import('/@/layout/upgrade/index.vue'));
const Sponsors = defineAsyncComponent(() => import('/@/layout/sponsors/index.vue'));
// 定义变量内容 // 定义变量内容
const { messages, locale } = useI18n(); const { messages, locale } = useI18n();
@ -32,6 +36,21 @@ const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig); const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置锁屏时组件显示隐藏
const setLockScreen = computed(() => {
// 防止锁屏后,刷新出现不相关界面
// https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
return themeConfig.value.isLockScreen ? themeConfig.value.lockScreenTime > 1 : themeConfig.value.lockScreenTime >= 0;
});
// 获取版本号
const getVersion = computed(() => {
let isVersion = false;
if (route.path !== '/login') {
// @ts-ignore
if ((Local.get('version') && Local.get('version') !== __NEXT_VERSION__) || !Local.get('version')) isVersion = true;
}
return isVersion;
});
// 获取全局组件大小 // 获取全局组件大小
const getGlobalComponentSize = computed(() => { const getGlobalComponentSize = computed(() => {
return other.globalComponentSize(); return other.globalComponentSize();

View File

@ -1,17 +1,27 @@
import request from '/@/utils/request'; import request from '/@/utils/request';
/** /**
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
*
* 登录api接口集合 * 登录api接口集合
* @method signIn 用户登录 * @method signIn 用户登录
* @method signOut 用户退出登录 * @method signOut 用户退出登录
*/ */
export function useLoginApi() { export function useLoginApi() {
return { return {
signIn: (params: object) => { signIn: (data: object) => {
return request.post('/user/signIn', params); return request({
url: '/user/signIn',
method: 'post',
data,
});
}, },
signOut: (params: object) => { signOut: (data: object) => {
return request.post('/user/signOut', params); return request({
url: '/user/signOut',
method: 'post',
data,
});
}, },
}; };
} }

View File

@ -3,6 +3,8 @@ import request from '/@/utils/request';
/** /**
* 以下为模拟接口地址gitee 的不通,就换自己的真实接口地址 * 以下为模拟接口地址gitee 的不通,就换自己的真实接口地址
* *
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
*
* 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu * 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由isRequestRoutes 为 true则开启后端控制路由 * 后端控制路由isRequestRoutes 为 true则开启后端控制路由
* @method getAdminMenu 获取后端动态路由菜单(admin) * @method getAdminMenu 获取后端动态路由菜单(admin)
@ -11,10 +13,18 @@ import request from '/@/utils/request';
export function useMenuApi() { export function useMenuApi() {
return { return {
getAdminMenu: (params?: object) => { getAdminMenu: (params?: object) => {
return request.get('/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json', params); return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
method: 'get',
params,
});
}, },
getTestMenu: (params?: object) => { getTestMenu: (params?: object) => {
return request.get('/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json', params); return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
method: 'get',
params,
});
}, },
}; };
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -88,4 +88,14 @@ watch(
deep: true, deep: true,
} }
); );
// 监听双向绑定值改变,用于回显
watch(
() => props.getHtml,
(val) => {
state.editorVal = val;
},
{
deep: true,
}
);
</script> </script>

View File

@ -1,67 +1,48 @@
<template> <template>
<div class="icon-selector w100 h100"> <div class="icon-selector w100 h100">
<el-input
v-model="state.fontIconSearch"
:placeholder="state.fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
class="font14"
v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
/>
<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
</template>
</el-input>
<el-popover <el-popover
placement="bottom" placement="bottom"
:width="state.fontIconWidth" :width="state.fontIconWidth"
trigger="click"
transition="el-zoom-in-top" transition="el-zoom-in-top"
popper-class="icon-selector-popper" popper-class="icon-selector-popper"
@show="onPopoverShow" trigger="click"
:virtual-ref="inputWidthRef"
virtual-triggering
> >
<template #reference>
<el-input
v-model="state.fontIconSearch"
:placeholder="state.fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
class="font14"
v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
/>
<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
</template>
</el-input>
</template>
<template #default> <template #default>
<div class="icon-selector-warp"> <div class="icon-selector-warp">
<div class="icon-selector-warp-title flex"> <div class="icon-selector-warp-title">{{ title }}</div>
<div class="flex-auto">{{ title }}</div> <el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<div class="icon-selector-warp-title-tab" v-if="type === 'all'"> <el-tab-pane lazy label="ali" name="ali">
<span :class="{ 'span-active': state.fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标"> <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
ali </el-tab-pane>
</span> <el-tab-pane lazy label="ele" name="ele">
<span :class="{ 'span-active': state.fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标"> <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
ele </el-tab-pane>
</span> <el-tab-pane lazy label="awe" name="awe">
<span :class="{ 'span-active': state.fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标"> <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
awe </el-tab-pane>
</span> </el-tabs>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': state.fontIconPrefix === v }">
<div class="flex-margin">
<div class="icon-selector-warp-item-value">
<SvgIcon :name="v" />
</div>
</div>
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
</el-scrollbar>
</div>
</div> </div>
</template> </template>
</el-popover> </el-popover>
@ -69,8 +50,10 @@
</template> </template>
<script setup lang="ts" name="iconSelector"> <script setup lang="ts" name="iconSelector">
import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue'; import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
import type { TabsPaneContext } from 'element-plus';
import initIconfont from '/@/utils/getStyleSheets'; import initIconfont from '/@/utils/getStyleSheets';
import '/@/theme/iconSelector.scss';
// 定义父组件传过来的值 // 定义父组件传过来的值
const props = defineProps({ const props = defineProps({
@ -94,11 +77,6 @@ const props = defineProps({
type: String, type: String,
default: () => '请选择图标', default: () => '请选择图标',
}, },
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用 // 禁用
disabled: { disabled: {
type: Boolean, type: Boolean,
@ -123,18 +101,22 @@ const props = defineProps({
// 定义子组件向父组件传值/事件 // 定义子组件向父组件传值/事件
const emit = defineEmits(['update:modelValue', 'get', 'clear']); const emit = defineEmits(['update:modelValue', 'get', 'clear']);
// 引入组件
const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/list.vue'));
// 定义变量内容 // 定义变量内容
const inputWidthRef = ref(); const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state = reactive({ const state = reactive({
fontIconPrefix: '', fontIconPrefix: '',
fontIconWidth: 0, fontIconWidth: 0,
fontIconSearch: '', fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '', fontIconPlaceholder: '',
fontIconType: 'ali', fontIconTabActive: 'ali',
fontIconShow: true, fontIconList: {
ali: [],
ele: [],
awe: [],
},
}); });
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值 // 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
@ -145,32 +127,91 @@ const onIconFocus = () => {
}; };
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值 // 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => { const onIconBlur = () => {
const list = fontIconTabNameList();
setTimeout(() => { setTimeout(() => {
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch); const icon = list.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = ''; if (icon.length <= 0) state.fontIconSearch = '';
}, 300); }, 300);
}; };
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
const list = fontIconTabNameList();
if (!state.fontIconSearch) return list;
let search = state.fontIconSearch.trim().toLowerCase();
return list.filter((item: string) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 根据 tab name 类型设置图标
const fontIconTabNameList = () => {
let iconList: any = [];
if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
return iconList;
};
// 处理 icon 双向绑定数值回显 // 处理 icon 双向绑定数值回显
const initModeValueEcho = () => { const initModeValueEcho = () => {
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder); if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
(<string | undefined>state.fontIconPlaceholder) = props.modelValue; (<string | undefined>state.fontIconPlaceholder) = props.modelValue;
(<string | undefined>state.fontIconPrefix) = props.modelValue; (<string | undefined>state.fontIconPrefix) = props.modelValue;
}; };
// 处理 icon type 类型为 all 时,类型 ali、ele、awe 回显问题 // 处理 icon 类型用于回显时tab 高亮与初始化数据
const initFontIconTypeEcho = () => { const initFontIconName = () => {
if (props.modelValue!.indexOf('iconfont') > -1) onIconChange('ali'); let name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) onIconChange('ele'); if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('fa') > -1) onIconChange('awe'); else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else onIconChange('ali'); else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
// 初始化 tab 高亮回显
state.fontIconTabActive = name;
return name;
};
// 初始化数据
const initFontIconData = async (name: string) => {
if (name === 'ali') {
// 阿里字体图标使用 `iconfont xxx`
if (state.fontIconList.ali.length > 0) return;
await initIconfont.ali().then((res: any) => {
state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
});
} else if (name === 'ele') {
// element plus 图标
if (state.fontIconList.ele.length > 0) return;
await initIconfont.ele().then((res: any) => {
state.fontIconList.ele = res;
});
} else if (name === 'awe') {
// fontawesome字体图标使用 `fa xxx`
if (state.fontIconList.awe.length > 0) return;
await initIconfont.awe().then((res: any) => {
state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
};
// 图标点击切换
const onIconClick = (pane: TabsPaneContext) => {
initFontIconData(pane.paneName as string);
inputWidthRef.value.focus();
};
// 获取当前点击的 icon 图标
const onColClick = (v: string) => {
state.fontIconPlaceholder = v;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
inputWidthRef.value.focus();
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
}; };
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
if (!state.fontIconSearch) return state.fontIconSheetsList;
let search = state.fontIconSearch.trim().toLowerCase();
return state.fontIconSheetsList.filter((item: string) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 获取 input 的宽度 // 获取 input 的宽度
const getInputWidth = () => { const getInputWidth = () => {
nextTick(() => { nextTick(() => {
@ -183,56 +224,9 @@ const initResize = () => {
getInputWidth(); getInputWidth();
}); });
}; };
// 初始化数据
const initFontIconData = async (type: string) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res: any) => {
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i: string) => `iconfont ${i}`);
});
} else if (type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconSheetsList = res;
});
} else if (type === 'awe') {
await initIconfont.awe().then((res: any) => {
// fontawesome字体图标使用 `fa xxx`
state.fontIconSheetsList = res.map((i: string) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
};
// 图标点击切换
const onIconChange = (type: string) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v: string) => {
state.fontIconPlaceholder = v;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 监听 Popover 打开,用于双向绑定值回显
const onPopoverShow = () => {
initModeValueEcho();
initFontIconTypeEcho();
};
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
initModeValueEcho(); initFontIconData(initFontIconName());
initResize(); initResize();
getInputWidth(); getInputWidth();
}); });
@ -241,6 +235,7 @@ watch(
() => props.modelValue, () => props.modelValue,
() => { () => {
initModeValueEcho(); initModeValueEcho();
initFontIconName();
} }
); );
</script> </script>

View File

@ -0,0 +1,84 @@
<template>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="props.list.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
<SvgIcon :name="v" />
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
</el-scrollbar>
</div>
</template>
<script setup lang="ts" name="iconSelectorList">
// 定义父组件传过来的值
const props = defineProps({
// 图标列表数据
list: {
type: Array,
default: () => [],
},
// 自定义空状态描述文字
empty: {
type: String,
default: () => '无相关图标',
},
// 高亮当前选中图标
prefix: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['get-icon']);
// 当前 icon 图标点击时
const onColClick = (v: unknown | string) => {
emit('get-icon', v);
};
</script>
<style scoped lang="scss">
.icon-selector-warp-row {
height: 230px;
overflow: hidden;
.el-row {
padding: 15px;
}
.el-scrollbar__bar.is-horizontal {
display: none;
}
.icon-selector-warp-item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--el-border-color);
border-radius: 5px;
margin-bottom: 10px;
height: 30px;
i {
font-size: 20px;
color: var(--el-text-color-regular);
}
&:hover {
cursor: pointer;
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
.icon-selector-active {
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
i {
color: var(--el-color-primary);
}
}
}
</style>

View File

@ -21,8 +21,15 @@
:label="item.title" :label="item.title"
> >
<template v-slot="scope"> <template v-slot="scope">
<template v-if="item.key === 'image'"> <template v-if="item.type === 'image'">
<img :src="scope.row[item.key]" :width="item.width" :height="item.height" /> <el-image
:style="{ width: `${item.width}px`, height: `${item.height}px` }"
:src="scope.row[item.key]"
:zoom-rate="1.2"
:preview-src-list="[scope.row[item.key]]"
preview-teleported
fit="cover"
/>
</template> </template>
<template v-else> <template v-else>
{{ scope.row[item.key] }} {{ scope.row[item.key] }}
@ -56,6 +63,7 @@
> >
</el-pagination> </el-pagination>
<div class="table-footer-tool"> <div class="table-footer-tool">
<SvgIcon name="iconfont icon-dayin" :size="19" title="打印" @click="onPrintTable" />
<SvgIcon name="iconfont icon-yunxiazai_o" :size="22" title="导出" @click="onImportTable" /> <SvgIcon name="iconfont icon-yunxiazai_o" :size="22" title="导出" @click="onImportTable" />
<SvgIcon name="iconfont icon-shuaxin" :size="22" title="刷新" @click="onRefreshTable" /> <SvgIcon name="iconfont icon-shuaxin" :size="22" title="刷新" @click="onRefreshTable" />
<el-popover <el-popover
@ -71,26 +79,28 @@
<SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" /> <SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
</template> </template>
<template #default> <template #default>
<div ref="toolSetRef"> <div class="tool-box">
<div class="tool-box"> <el-tooltip content="拖动进行排序" placement="top-start">
<el-tooltip content="拖动进行排序" placement="top-start"> <SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
<SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" /> </el-tooltip>
</el-tooltip> <el-checkbox
<el-checkbox v-model="state.checkListAll"
v-model="state.checkListAll" :indeterminate="state.checkListIndeterminate"
:indeterminate="state.checkListIndeterminate" class="ml10 mr1"
class="ml10 mr1" label="列显示"
label="列显示" @change="onCheckAllChange"
@change="onCheckAllChange" />
/> <el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" />
<el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" /> <el-checkbox v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
<el-checkbox v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
</div>
<div class="tool-item" v-for="v in header" :key="v.key" :data-key="v.key">
<i class="fa fa-arrows-alt handle cursor-pointer"></i>
<el-checkbox v-model="v.isCheck" class="ml12 mr8" :label="v.title" @change="onCheckChange" />
</div>
</div> </div>
<el-scrollbar>
<div ref="toolSetRef" class="tool-sortable">
<div class="tool-sortable-item" v-for="v in header" :key="v.key" :data-key="v.key">
<i class="fa fa-arrows-alt handle cursor-pointer"></i>
<el-checkbox v-model="v.isCheck" size="default" class="ml12 mr8" :label="v.title" @change="onCheckChange" />
</div>
</div>
</el-scrollbar>
</template> </template>
</el-popover> </el-popover>
</div> </div>
@ -101,6 +111,7 @@
<script setup lang="ts" name="netxTable"> <script setup lang="ts" name="netxTable">
import { reactive, computed, nextTick, ref } from 'vue'; import { reactive, computed, nextTick, ref } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import printJs from 'print-js';
import table2excel from 'js-table2excel'; import table2excel from 'js-table2excel';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -119,16 +130,16 @@ const props = defineProps({
type: Array<EmptyObjectType>, type: Array<EmptyObjectType>,
default: () => [], default: () => [],
}, },
// 搜索参数
param: {
type: Object,
default: () => {},
},
// 配置项 // 配置项
config: { config: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
// 打印标题
printName: {
type: String,
default: () => '',
},
}); });
// 定义子组件向父组件传值/事件 // 定义子组件向父组件传值/事件
@ -161,7 +172,7 @@ const setHeader = computed(() => {
return props.header.filter((v) => v.isCheck); return props.header.filter((v) => v.isCheck);
}); });
// tool 列显示全选改变时 // tool 列显示全选改变时
const onCheckAllChange = (val: boolean) => { const onCheckAllChange = <T>(val: T) => {
if (val) props.header.forEach((v) => (v.isCheck = true)); if (val) props.header.forEach((v) => (v.isCheck = true));
else props.header.forEach((v) => (v.isCheck = false)); else props.header.forEach((v) => (v.isCheck = false));
state.checkListIndeterminate = false; state.checkListIndeterminate = false;
@ -196,6 +207,37 @@ const pageReset = () => {
state.page.pageSize = 10; state.page.pageSize = 10;
emit('pageChange', state.page); emit('pageChange', state.page);
}; };
// 打印
const onPrintTable = () => {
// https://printjs.crabbly.com/#documentation
// 自定义打印
let tableTh = '';
let tableTrTd = '';
let tableTd: any = {};
// 表头
props.header.forEach((v) => {
tableTh += `<th class="table-th">${v.title}</th>`;
});
// 表格内容
props.data.forEach((val, key) => {
if (!tableTd[key]) tableTd[key] = [];
props.header.forEach((v) => {
if (v.type === 'text') {
tableTd[key].push(`<td class="table-th table-center">${val[v.key]}</td>`);
} else if (v.type === 'image') {
tableTd[key].push(`<td class="table-th table-center"><img src="${val[v.key]}" style="width:${v.width}px;height:${v.height}px;"/></td>`);
}
});
tableTrTd += `<tr>${tableTd[key].join('')}</tr>`;
});
// 打印
printJs({
printable: `<div style=display:flex;flex-direction:column;text-align:center><h3>${props.printName}</h3></div><table border=1 cellspacing=0><tr>${tableTh}${tableTrTd}</table>`,
type: 'raw-html',
css: ['//at.alicdn.com/t/c/font_2298093_rnp72ifj3ba.css', '//unpkg.com/element-plus/dist/index.css'],
style: `@media print{.mb15{margin-bottom:15px;}.el-button--small i.iconfont{font-size: 12px !important;margin-right: 5px;}}; .table-th{word-break: break-all;white-space: pre-wrap;}.table-center{text-align: center;}`,
});
};
// 导出 // 导出
const onImportTable = () => { const onImportTable = () => {
if (state.selectlist.length <= 0) return ElMessage.warning('请先选择要导出的数据'); if (state.selectlist.length <= 0) return ElMessage.warning('请先选择要导出的数据');
@ -214,7 +256,7 @@ const onSetTable = () => {
animation: 150, animation: 150,
onEnd: () => { onEnd: () => {
const headerList: EmptyObjectType[] = []; const headerList: EmptyObjectType[] = [];
sortable.toArray().forEach((val) => { sortable.toArray().forEach((val: string) => {
props.header.forEach((v) => { props.header.forEach((v) => {
if (v.key === val) headerList.push({ ...v }); if (v.key === val) headerList.push({ ...v });
}); });

View File

@ -2,53 +2,53 @@ import { createI18n } from 'vue-i18n';
import pinia from '/@/stores/index'; import pinia from '/@/stores/index';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig'; 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 nextZhcn from '/@/i18n/lang/zh-cn';
import nextEn from '/@/i18n/lang/en';
import nextZhtw from '/@/i18n/lang/zh-tw';
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
import pagesLoginEn from '/@/i18n/pages/login/en';
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
// 定义语言国际化内容 // 定义语言国际化内容
/** /**
* 说明: * 说明:
* /src/i18n/lang 下的 ts 为框架的国际化内容 * 须在 pages 下新建文件夹(建议 `要国际化界面目录` 与 `i18n 目录` 相同,方便查找),
* /src/i18n/pages 下的 ts 为各界面的国际化内容 * 注意国际化定义的字段,不要与原有的定义字段相同。
* 1、/src/i18n/lang 下的 ts 为框架的国际化内容
* 2、/src/i18n/pages 下的 ts 为各界面的国际化内容
*/ */
const messages = {
[zhcnLocale.name]: { // element plus 自带国际化
...zhcnLocale, import enLocale from 'element-plus/dist/locale/en';
message: { import zhcnLocale from 'element-plus/dist/locale/zh-cn';
...nextZhcn, import zhtwLocale from 'element-plus/dist/locale/zh-tw';
...pagesLoginZhcn,
...pagesFormI18nZhcn, // 定义变量内容
}, const messages: any = {};
}, const element: any = { en: enLocale, 'zh-cn': zhcnLocale, 'zh-tw': zhtwLocale };
[enLocale.name]: { const itemize: any = { en: [], 'zh-cn': [], 'zh-tw': [] };
...enLocale, const modules: Record<string, any> = import.meta.glob('./**/*.ts', { eager: true });
message: {
...nextEn, // 对自动引入的 modules 进行分类 en、zh-cn、zh-tw
...pagesLoginEn, // https://vitejs.cn/vite3-cn/guide/features.html#glob-import
...pagesFormI18nEn, for (const path in modules) {
}, const key = path.match(/(\S+)\/(\S+).ts/);
}, if (itemize[key![2]]) itemize[key![2]].push(modules[path].default);
[zhtwLocale.name]: { else itemize[key![2]] = modules[path];
...zhtwLocale, }
message: {
...nextZhtw, // 合并数组对象(非标准数组对象,数组中对象的每项 key、value 都不同)
...pagesLoginZhtw, function mergeArrObj(list: any, key: string) {
...pagesFormI18nZhtw, let obj = {};
}, list[key].forEach((i: EmptyObjectType) => {
}, obj = Object.assign({}, obj, i);
}; });
return obj;
}
// 处理最终格式
for (const key in itemize) {
messages[key] = {
name: key,
el: element[key].el,
message: mergeArrObj(itemize, key),
};
}
// 读取 pinia 默认语言 // 读取 pinia 默认语言
const stores = useThemeConfig(pinia); const stores = useThemeConfig(pinia);

View File

@ -137,6 +137,7 @@ export default {
twoIsTopBarColorGradual: 'Top bar gradient', twoIsTopBarColorGradual: 'Top bar gradient',
twoMenuBar: 'Menu background', twoMenuBar: 'Menu background',
twoMenuBarColor: 'Menu default font color', twoMenuBarColor: 'Menu default font color',
twoMenuBarActiveColor: 'Menu Highlight Color',
twoIsMenuBarColorGradual: 'Menu gradient', twoIsMenuBarColorGradual: 'Menu gradient',
twoColumnsMenuBar: 'Column menu background', twoColumnsMenuBar: 'Column menu background',
twoColumnsMenuBarColor: 'Default font color bar menu', twoColumnsMenuBarColor: 'Default font color bar menu',
@ -180,4 +181,12 @@ export default {
copyTextSuccess: 'Copy succeeded!', copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!', copyTextError: 'Copy failed!',
}, },
upgrade: {
title: 'New version',
msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
desc: 'Prompt: Update will restore the default configuration',
btnOne: 'Cruel refusal',
btnTwo: 'Update now',
btnTwoLoading: 'Updating',
},
}; };

View File

@ -137,6 +137,7 @@ export default {
twoIsTopBarColorGradual: '顶栏背景渐变', twoIsTopBarColorGradual: '顶栏背景渐变',
twoMenuBar: '菜单背景', twoMenuBar: '菜单背景',
twoMenuBarColor: '菜单默认字体颜色', twoMenuBarColor: '菜单默认字体颜色',
twoMenuBarActiveColor: '菜单高亮背景色',
twoIsMenuBarColorGradual: '菜单背景渐变', twoIsMenuBarColorGradual: '菜单背景渐变',
twoColumnsMenuBar: '分栏菜单背景', twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色', twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
@ -180,4 +181,12 @@ export default {
copyTextSuccess: '复制成功!', copyTextSuccess: '复制成功!',
copyTextError: '复制失败!', copyTextError: '复制失败!',
}, },
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配置',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
}; };

View File

@ -137,6 +137,7 @@ export default {
twoIsTopBarColorGradual: '頂欄背景漸變', twoIsTopBarColorGradual: '頂欄背景漸變',
twoMenuBar: '選單背景', twoMenuBar: '選單背景',
twoMenuBarColor: '選單默認字體顏色', twoMenuBarColor: '選單默認字體顏色',
twoMenuBarActiveColor: '選單高亮背景色',
twoIsMenuBarColorGradual: '選單背景漸變', twoIsMenuBarColorGradual: '選單背景漸變',
twoColumnsMenuBar: '分欄選單背景', twoColumnsMenuBar: '分欄選單背景',
twoColumnsMenuBarColor: '分欄選單默認字體顏色', twoColumnsMenuBarColor: '分欄選單默認字體顏色',
@ -180,4 +181,12 @@ export default {
copyTextSuccess: '複製成功!', copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!', copyTextError: '複製失敗!',
}, },
upgrade: {
title: '新版本陞級',
msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
desc: '提示:更新會還原默認配寘',
btnOne: '殘忍拒絕',
btnTwo: '馬上更新',
btnTwoLoading: '更新中',
},
}; };

View File

@ -12,7 +12,6 @@
<script setup lang="ts" name="layoutAside"> <script setup lang="ts" name="layoutAside">
import { defineAsyncComponent, reactive, computed, watch, onBeforeMount, ref } from 'vue'; import { defineAsyncComponent, reactive, computed, watch, onBeforeMount, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
@ -56,8 +55,8 @@ const setCollapseStyle = computed(() => {
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close']; return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
} }
} else { } else {
if (layout === 'columns') { if (layout === 'columns' || layout === 'classic') {
// 分栏布局,菜单收起时宽度给 1px // 分栏布局、经典布局,菜单收起时宽度给 1px,防止切换动画消失
if (isCollapse) return [asideBrColor, 'layout-aside-pc-1']; if (isCollapse) return [asideBrColor, 'layout-aside-pc-1'];
else return [asideBrColor, 'layout-aside-pc-220']; else return [asideBrColor, 'layout-aside-pc-220'];
} else { } else {
@ -107,7 +106,8 @@ const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = themeConfig.value; let { layout } = themeConfig.value;
if (layout !== 'columns') return false; if (layout !== 'columns') return false;
if (!bool) mittBus.emit('restoreDefault'); if (!bool) mittBus.emit('restoreDefault');
stores.setColumnsMenuHover(bool); // 开启 `分栏菜单鼠标悬停预加载` 才设置,防止 columnsAside.vue 监听 pinia.state
if (themeConfig.value.isColumnsMenuHoverPreload) stores.setColumnsMenuHover(bool);
}; };
// 页面加载前 // 页面加载前
onBeforeMount(() => { onBeforeMount(() => {
@ -118,37 +118,41 @@ onBeforeMount(() => {
mittBus.on('setSendColumnsChildren', (res: MittMenu) => { mittBus.on('setSendColumnsChildren', (res: MittMenu) => {
state.menuList = res.children; state.menuList = res.children;
}); });
// 开启经典布局分割菜单时,设置菜单数据
mittBus.on('setSendClassicChildren', (res: MittMenu) => { mittBus.on('setSendClassicChildren', (res: MittMenu) => {
let { layout, isClassicSplitMenu } = themeConfig.value; let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) { if (layout === 'classic' && isClassicSplitMenu) {
// 经典布局分割菜单只要一项子级时,收起左侧导航菜单
res.children.length <= 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false);
state.menuList = []; state.menuList = [];
state.menuList = res.children; state.menuList = res.children;
} }
}); });
// 开启经典布局分割菜单时,重新处理菜单数据
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => { mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes(); setFilterRoutes();
}); });
// 监听窗口大小改变时(适配移动端)
mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => { mittBus.on('layoutMobileResize', (res: LayoutMobileResize) => {
initMenuFixed(res.clientWidth); initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode(); closeLayoutAsideMobileMode();
}); });
}); });
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度 // 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(themeConfig.value, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
});
// 监听 pinia 值的变化,动态赋值给菜单中
watch( watch(
pinia.state, () => [themeConfig.value.isShowLogoChange, themeConfig.value.isShowLogo, themeConfig.value.layout, themeConfig.value.isClassicSplitMenu],
(val) => { ([isShowLogoChange, isShowLogo, layout, isClassicSplitMenu]) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig; if (isShowLogoChange !== isShowLogo) {
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
if (layout === 'classic' && isClassicSplitMenu) return false; if (layout === 'classic' && isClassicSplitMenu) return false;
}
);
// 监听用户权限切换,用于演示 `权限管理 -> 前端控制 -> 页面权限` 权限切换不生效
watch(
() => routesList.value,
() => {
setFilterRoutes(); setFilterRoutes();
},
{
deep: true,
} }
); );
</script> </script>

View File

@ -5,7 +5,7 @@
<li <li
v-for="(v, k) in state.columnsAsideList" v-for="(v, k) in state.columnsAsideList"
:key="k" :key="k"
@click="onColumnsAsideMenuClick(v, k)" @click="onColumnsAsideMenuClick(v)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)" @mouseenter="onColumnsAsideMenuMouseenter(v, k)"
:ref=" :ref="
(el) => { (el) => {
@ -48,7 +48,6 @@
import { reactive, ref, onMounted, nextTick, watch, onUnmounted } from 'vue'; import { reactive, ref, onMounted, nextTick, watch, onUnmounted } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router'; import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt'; import mittBus from '/@/utils/mitt';
@ -74,15 +73,33 @@ const state = reactive<ColumnsAsideState>({
// 设置菜单高亮位置移动 // 设置菜单高亮位置移动
const setColumnsAsideMove = (k: number) => { const setColumnsAsideMove = (k: number) => {
if (k === undefined) return false;
state.liIndex = k; state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`; columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
}; };
// 菜单高亮点击事件 // 菜单高亮点击事件
const onColumnsAsideMenuClick = (v: RouteItem, k: number) => { const onColumnsAsideMenuClick = async (v: RouteItem) => {
setColumnsAsideMove(k);
let { path, redirect } = v; let { path, redirect } = v;
if (redirect) router.push(redirect); if (redirect) {
else router.push(path); onColumnsAsideDown(v.k);
if (route.path.startsWith(redirect)) mittBus.emit('setSendColumnsChildren', setSendChildren(redirect));
else router.push(redirect);
} else {
if (!v.children) {
router.push(path);
} else {
// 显示子级菜单
const resData: MittMenu = setSendChildren(path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item?.k);
mittBus.emit('setSendColumnsChildren', resData);
}
}
// 一个路由设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
if (!v.children) themeConfig.value.isCollapse = true;
else if (v.children.length > 1) themeConfig.value.isCollapse = false;
}; };
// 鼠标移入时,显示当前的子级菜单 // 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => { const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
@ -97,6 +114,7 @@ const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
}; };
// 鼠标移走时,显示原来的子级菜单 // 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => { const onColumnsAsideMenuMouseleave = async () => {
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
await stores.setColumnsNavHover(false); await stores.setColumnsNavHover(false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的 // 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => { setTimeout(() => {
@ -109,13 +127,23 @@ const onColumnsAsideDown = (k: number) => {
setColumnsAsideMove(k); setColumnsAsideMove(k);
}); });
}; };
// 设置只有一个路由时设置自动收起菜单
// https://gitee.com/lyt-top/vue-next-admin/issues/I6UW2I
const setMenuAutoCollaps = (path: string) => {
const resData: MittMenu = setSendChildren(path);
// https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
resData.children.length <= 1 ? (themeConfig.value.isCollapse = true) : (themeConfig.value.isCollapse = false);
return resData;
};
// 设置/过滤路由(非静态路由/是否显示在菜单中) // 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => { const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(routesList.value); state.columnsAsideList = filterRoutesFun(routesList.value);
const resData: MittMenu = setSendChildren(route.path); const resData: MittMenu = setMenuAutoCollaps(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item?.k); onColumnsAsideDown(resData.item?.k);
mittBus.emit('setSendColumnsChildren', resData); // 延迟 500 毫秒更新,防止 aside.vue 组件 setSendColumnsChildren 还没有注册
setTimeout(() => {
mittBus.emit('setSendColumnsChildren', resData);
}, 500);
}; };
// 传送当前子级数据到菜单中 // 传送当前子级数据到菜单中
const setSendChildren = (path: string) => { const setSendChildren = (path: string) => {
@ -168,15 +196,16 @@ onUnmounted(() => {
}); });
// 路由更新时 // 路由更新时
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
const resData = setMenuAutoCollaps(to.path);
setColumnsMenuHighlight(to.path); setColumnsMenuHighlight(to.path);
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path)); mittBus.emit('setSendColumnsChildren', resData);
}); });
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素 // 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch( watch(
pinia.state, [() => themeConfig.value.columnsAsideStyle, isColumnsMenuHover, isColumnsNavHover],
(val) => { () => {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0); themeConfig.value.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) { if (!isColumnsMenuHover.value && !isColumnsNavHover.value) {
state.liHoverIndex = null; state.liHoverIndex = null;
mittBus.emit('setSendColumnsChildren', setSendChildren(route.path)); mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else { } else {
@ -198,7 +227,8 @@ watch(
background: var(--next-bg-columnsMenuBar); background: var(--next-bg-columnsMenuBar);
ul { ul {
position: relative; position: relative;
.layout-columns-active { .layout-columns-active,
.layout-columns-active a {
color: var(--next-bg-columnsMenuBarColor) !important; color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }

View File

@ -213,7 +213,7 @@ onUnmounted(() => {
} }
.layout-lock-screen-img { .layout-lock-screen-img {
@extend .layout-lock-screen-fixed; @extend .layout-lock-screen-fixed;
background-image: url('https://img-blog.csdnimg.cn/afa9c317667f47d5bea34b85af45979e.png#pic_center'); background-image: url('https://i.hd-r.cn/e4a19d84364f185266666765ac21a5db.jpg');
background-size: 100% 100%; background-size: 100% 100%;
z-index: 9999991; z-index: 9999991;
} }

View File

@ -43,7 +43,7 @@ const initScrollBarHeight = () => {
setTimeout(() => { setTimeout(() => {
updateScrollbar(); updateScrollbar();
// '!' not null 断言操作符,不执行运行时检查 // '!' not null 断言操作符,不执行运行时检查
layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0; if (layoutMainRef.value) layoutMainRef.value!.layoutMainScrollbarRef.wrapRef.scrollTop = 0;
}, 500); }, 500);
}); });
}; };
@ -58,11 +58,13 @@ watch(
initScrollBarHeight(); initScrollBarHeight();
} }
); );
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度 // 监听 themeConfig isTagsview 配置文件的变化,更新菜单 el-scrollbar 的高度
watch( watch(
themeConfig, () => themeConfig.value.isTagsview,
() => { () => {
updateScrollbar(); nextTick(() => {
updateScrollbar();
});
}, },
{ {
deep: true, deep: true,

View File

@ -35,7 +35,7 @@ const updateScrollbar = () => {
// 更新父级 scrollbar // 更新父级 scrollbar
layoutScrollbarRef.value.update(); layoutScrollbarRef.value.update();
// 更新子级 scrollbar // 更新子级 scrollbar
layoutMainRef.value!.layoutMainScrollbarRef.update(); layoutMainRef.value && layoutMainRef.value!.layoutMainScrollbarRef.update();
}; };
// 重置滚动条高度,由于组件是异步引入的 // 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => { const initScrollBarHeight = () => {
@ -60,9 +60,11 @@ watch(
); );
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度 // 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch( watch(
themeConfig, () => [themeConfig.value.isTagsview, themeConfig.value.isFixedHeader],
() => { () => {
updateScrollbar(); nextTick(() => {
updateScrollbar();
});
}, },
{ {
deep: true, deep: true,

View File

@ -60,9 +60,11 @@ watch(
); );
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度 // 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch( watch(
themeConfig, () => [themeConfig.value.isTagsview, themeConfig.value.isFixedHeader],
() => { () => {
updateScrollbar(); nextTick(() => {
updateScrollbar();
});
}, },
{ {
deep: true, deep: true,

View File

@ -47,9 +47,11 @@ watch(
); );
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度 // 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch( watch(
themeConfig, () => themeConfig.value.isTagsview,
() => { () => {
updateScrollbar(); nextTick(() => {
updateScrollbar();
});
}, },
{ {
deep: true, deep: true,

View File

@ -11,7 +11,7 @@ import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
// 引入组件 // 引入组件
const BreadcrumbIndex = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')); const BreadcrumbIndex = defineAsyncComponent(() => import('/@/layout/navBars/topBar/index.vue'));
const TagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')); const TagsView = defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue'));
// 定义变量内容 // 定义变量内容

View File

@ -52,10 +52,10 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import { useKeepALiveNames } from '/@/stores/keepAliveNames'; import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useRoutesList } from '/@/stores/routesList';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation'; import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other'; import other from '/@/utils/other';
@ -72,8 +72,10 @@ const tagsUlRef = ref();
const stores = useTagsViewRoutes(); const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes(); const storesTagsViewRoutes = useTagsViewRoutes();
const storesRoutesList = useRoutesList();
const { themeConfig } = storeToRefs(storesThemeConfig); const { themeConfig } = storeToRefs(storesThemeConfig);
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes); const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
const { routesList } = storeToRefs(storesRoutesList);
const storesKeepALiveNames = useKeepALiveNames(); const storesKeepALiveNames = useKeepALiveNames();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -382,6 +384,11 @@ const onMousedownMenu = (v: RouteItem, e: MouseEvent) => {
const onTagsClick = (v: RouteItem, k: number) => { const onTagsClick = (v: RouteItem, k: number) => {
state.tagsRefsIndex = k; state.tagsRefsIndex = k;
router.push(v); router.push(v);
// 分栏布局时,收起/展开菜单
if (getThemeConfig.value.layout === 'columns') {
const item: RouteItem = routesList.value.find((r: RouteItem) => r.path.indexOf(`/${v.path.split('/')[1]}`) > -1);
!item.children ? (getThemeConfig.value.isCollapse = true) : (getThemeConfig.value.isCollapse = false);
}
}; };
// 处理 url地址栏链接有参数时tagsview 右键菜单刷新功能失效问题,感谢 @ZzZz-RIPPER、@dejavuuuuu // 处理 url地址栏链接有参数时tagsview 右键菜单刷新功能失效问题,感谢 @ZzZz-RIPPER、@dejavuuuuu
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO // https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
@ -578,9 +585,9 @@ onBeforeRouteUpdate(async (to) => {
}); });
// 监听路由的变化,动态赋值给 tagsView // 监听路由的变化,动态赋值给 tagsView
watch( watch(
pinia.state, () => tagsViewRoutes.value,
(val) => { (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false; if (val.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes(); getTagsViewRoutes();
}, },
{ {

View File

@ -16,8 +16,8 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt'; import mittBus from '/@/utils/mitt';
// //
const Breadcrumb = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')); const Breadcrumb = defineAsyncComponent(() => import('/@/layout/navBars/topBar/breadcrumb.vue'));
const User = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')); const User = defineAsyncComponent(() => import('/@/layout/navBars/topBar/user.vue'));
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue')); const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
const Horizontal = defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')); const Horizontal = defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue'));

View File

@ -102,11 +102,14 @@ defineExpose({
.layout-search-dialog { .layout-search-dialog {
position: relative; position: relative;
:deep(.el-dialog) { :deep(.el-dialog) {
background: unset;
box-shadow: unset;
.el-dialog__header, .el-dialog__header,
.el-dialog__body { .el-dialog__body {
display: none; display: none;
} }
.el-dialog__footer { .el-dialog__footer {
width: 100%;
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);

View File

@ -59,6 +59,17 @@
<el-color-picker v-model="getThemeConfig.menuBarColor" size="default" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker> <el-color-picker v-model="getThemeConfig.menuBarColor" size="default" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
</div> </div>
</div> </div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoMenuBarActiveColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker
v-model="getThemeConfig.menuBarActiveColor"
size="default"
show-alpha
@change="onBgColorPickerChange('menuBarActiveColor')"
/>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14"> <div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorGradual') }}</div> <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsMenuBarColorGradual') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value"> <div class="layout-breadcrumb-seting-bar-flex-value">
@ -427,7 +438,7 @@ import { useThemeConfig } from '/@/stores/themeConfig';
import { useChangeColor } from '/@/utils/theme'; import { useChangeColor } from '/@/utils/theme';
import { verifyAndSpace } from '/@/utils/toolsValidate'; import { verifyAndSpace } from '/@/utils/toolsValidate';
import { Local } from '/@/utils/storage'; import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark'; import Watermark from '/@/utils/watermark';
import commonFunction from '/@/utils/commonFunction'; import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other'; import other from '/@/utils/other';
import mittBus from '/@/utils/mitt'; import mittBus from '/@/utils/mitt';
@ -483,14 +494,16 @@ const onColumnsMenuBarGradualChange = () => {
}; };
// 2 / --> // 2 / -->
const setGraduaFun = (el: string, bool: boolean, color: string) => { const setGraduaFun = (el: string, bool: boolean, color: string) => {
setTimeout(() => { nextTick(() => {
let els = document.querySelector(el); setTimeout(() => {
if (!els) return false; let els = document.querySelector(el);
document.documentElement.style.setProperty('--el-menu-bg-color', document.documentElement.style.getPropertyValue('--next-bg-menuBar')); if (!els) return false;
if (bool) els.setAttribute('style', `background:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)}) !important;`); document.documentElement.style.setProperty('--el-menu-bg-color', document.documentElement.style.getPropertyValue('--next-bg-menuBar'));
else els.setAttribute('style', ``); if (bool) els.setAttribute('style', `background:linear-gradient(to bottom , ${color}, ${getLightColor(color, 0.5)})`);
setLocalThemeConfig(); else els.setAttribute('style', ``);
}, 200); setLocalThemeConfig();
}, 300);
});
}; };
// 2 -> // 2 ->
const onColumnsMenuHoverPreloadChange = () => { const onColumnsMenuHoverPreloadChange = () => {
@ -577,6 +590,7 @@ const onSetLayout = (layout: string) => {
const initLayoutChangeFun = () => { const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar'); onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor'); onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('menuBarActiveColor');
onBgColorPickerChange('topBar'); onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor'); onBgColorPickerChange('topBarColor');
onBgColorPickerChange('columnsMenuBar'); onBgColorPickerChange('columnsMenuBar');
@ -619,6 +633,8 @@ const onCopyConfigClick = () => {
const onResetConfigClick = () => { const onResetConfigClick = () => {
Local.clear(); Local.clear();
window.location.reload(); window.location.reload();
// @ts-ignore
Local.set('version', __NEXT_VERSION__);
}; };
// //
const initSetStyle = () => { const initSetStyle = () => {

View File

@ -36,20 +36,25 @@
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick"> <div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i> <i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div> </div>
<div class="layout-navbars-breadcrumb-user-icon"> <div class="layout-navbars-breadcrumb-user-icon" ref="userNewsBadgeRef" v-click-outside="onUserNewsClick">
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false"> <el-badge :is-dot="true">
<template #reference> <el-icon :title="$t('message.user.title4')">
<el-badge :is-dot="true"> <ele-Bell />
<el-icon :title="$t('message.user.title4')"> </el-icon>
<ele-Bell /> </el-badge>
</el-icon>
</el-badge>
</template>
<template #default>
<UserNews />
</template>
</el-popover>
</div> </div>
<el-popover
ref="userNewsRef"
:virtual-ref="userNewsBadgeRef"
placement="bottom"
trigger="click"
transition="el-zoom-in-top"
virtual-triggering
:width="300"
:persistent="false"
>
<UserNews />
</el-popover>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick"> <div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i <i
class="iconfont" class="iconfont"
@ -81,9 +86,9 @@
</template> </template>
<script setup lang="ts" name="layoutBreadcrumbUser"> <script setup lang="ts" name="layoutBreadcrumbUser">
import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue'; import { defineAsyncComponent, ref, unref, computed, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage, ClickOutside as vClickOutside } from 'element-plus';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -94,10 +99,12 @@ import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage'; import { Session, Local } from '/@/utils/storage';
// //
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue')); const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/topBar/userNews.vue'));
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue')); const Search = defineAsyncComponent(() => import('/@/layout/navBars/topBar/search.vue'));
// //
const userNewsRef = ref();
const userNewsBadgeRef = ref();
const { locale, t } = useI18n(); const { locale, t } = useI18n();
const router = useRouter(); const router = useRouter();
const stores = useUserInfo(); const stores = useUserInfo();
@ -132,6 +139,10 @@ const onScreenfullClick = () => {
else state.isScreenfull = false; else state.isScreenfull = false;
}); });
}; };
//
const onUserNewsClick = () => {
unref(userNewsRef).popperRef?.delayHide?.();
};
// icon // icon
const onLayoutSetingClick = () => { const onLayoutSetingClick = () => {
mittBus.emit('openSetingsDrawer'); mittBus.emit('openSetingsDrawer');
@ -199,7 +210,7 @@ const onLanguageChange = (lang: string) => {
}; };
// /i18n // /i18n
const initI18nOrSize = (value: string, attr: string) => { const initI18nOrSize = (value: string, attr: string) => {
state[attr] = Local.get('themeConfig')[value]; (<any>state)[attr] = Local.get('themeConfig')[value];
}; };
// //
onMounted(() => { onMounted(() => {

View File

@ -1,37 +1,35 @@
<template> <template>
<div class="el-menu-horizontal-warp"> <div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef"> <el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal">
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal"> <template v-for="val in menuLists">
<template v-for="val in menuLists"> <el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path"> <template #title>
<template #title> <SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" /> <SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span> {{ $t(val.meta.title) }}
</template> </template>
<SubItem :chil="val.children" /> <template #title v-else>
</el-sub-menu> <a class="w100" @click.prevent="onALinkClick(val)">
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="val.meta.icon" /> <SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }} {{ $t(val.meta.title) }}
</template> </a>
<template #title v-else> </template>
<a class="w100" @click.prevent="onALinkClick(val)"> </el-menu-item>
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</template> </template>
</el-menu> </template>
</el-scrollbar> </el-menu>
</div> </div>
</template> </template>
<script setup lang="ts" name="navMenuHorizontal"> <script setup lang="ts" name="navMenuHorizontal">
import { defineAsyncComponent, reactive, computed, onMounted, nextTick, onBeforeMount, ref } from 'vue'; import { defineAsyncComponent, reactive, computed, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router'; import { useRoute, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
@ -52,7 +50,6 @@ const props = defineProps({
}); });
// 定义变量内容 // 定义变量内容
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList(); const stores = useRoutesList();
const storesThemeConfig = useThemeConfig(); const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores); const { routesList } = storeToRefs(stores);
@ -66,19 +63,6 @@ const state = reactive({
const menuLists = computed(() => { const menuLists = computed(() => {
return <RouteItems>props.menuList; return <RouteItems>props.menuList;
}); });
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: WheelEventType) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数 // 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => { const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr return arr
@ -122,10 +106,6 @@ const onALinkClick = (val: RouteItem) => {
onBeforeMount(() => { onBeforeMount(() => {
setCurrentRouterHighlight(route); setCurrentRouterHighlight(route);
}); });
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时 // 路由更新时
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G // 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G

View File

@ -91,9 +91,9 @@ onBeforeRouteUpdate((to) => {
}); });
// 设置菜单的收起/展开 // 设置菜单的收起/展开
watch( watch(
themeConfig.value, () => themeConfig.value.isCollapse,
() => { (isCollapse) => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse); document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = isCollapse);
}, },
{ {
immediate: true, immediate: true,

View File

@ -2,7 +2,7 @@
<div class="layout-padding layout-padding-unset layout-iframe"> <div class="layout-padding layout-padding-unset layout-iframe">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white"> <div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
<transition-group :name="name" mode="out-in"> <transition-group :name="name">
<iframe <iframe
:src="v.meta.isLink" :src="v.meta.isLink"
:key="v.path" :key="v.path"

View File

@ -80,9 +80,13 @@ onMounted(() => {
getIframeListRoutes(); getIframeListRoutes();
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75 // https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK // https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
// https://gitee.com/lyt-top/vue-next-admin/pulls/40
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
if (themeConfig.value.isCacheTagsView) cachedViews.value = Session.get('tagsViewList')?.map((item: RouteItem) => item.name); if (themeConfig.value.isCacheTagsView) {
let tagsViewArr: RouteItem[] = Session.get('tagsViewList') || [];
cachedViews.value = tagsViewArr.filter((item) => item.meta?.isKeepAlive).map((item) => item.name as string);
}
}, 0); }, 0);
}); });
}); });

View File

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

View File

@ -0,0 +1,152 @@
<template>
<div class="upgrade-dialog">
<el-dialog
v-model="state.isUpgrade"
width="300px"
destroy-on-close
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="upgrade-title">
<div class="upgrade-title-warp">
<span class="upgrade-title-warp-txt">{{ $t('message.upgrade.title') }}</span>
<span class="upgrade-title-warp-version">v{{ state.version }}</span>
</div>
</div>
<div class="upgrade-content">
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
<div class="mt5">
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md" target="_black">
CHANGELOG.md
</el-link>
</div>
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
</div>
<div class="upgrade-btn">
<el-button round size="default" type="info" text @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="default" @click="onUpgrade" :loading="state.isLoading">{{ state.btnTxt }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="layoutUpgrade">
import { reactive, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
// 定义变量内容
const { t } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
isUpgrade: false,
// @ts-ignore
version: __NEXT_VERSION__,
isLoading: false,
btnTxt: '',
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
// 残忍拒绝
const onCancel = () => {
state.isUpgrade = false;
};
// 马上更新
const onUpgrade = () => {
state.isLoading = true;
state.btnTxt = t('message.upgrade.btnTwoLoading');
setTimeout(() => {
Local.clear();
window.location.reload();
Local.set('version', state.version);
}, 2000);
};
// 延迟显示,防止刷新时界面显示太快
const delayShow = () => {
setTimeout(() => {
state.isUpgrade = true;
}, 2000);
};
// 页面加载时
onMounted(() => {
delayShow();
setTimeout(() => {
state.btnTxt = t('message.upgrade.btnTwo');
}, 200);
});
</script>
<style scoped lang="scss">
.upgrade-dialog {
:deep(.el-dialog) {
padding: 0 !important;
.el-dialog__body {
padding: 0 !important;
}
.el-dialog__header {
display: none !important;
}
.upgrade-title {
text-align: center;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::after {
content: '';
position: absolute;
background-color: var(--el-color-primary-light-1);
width: 130%;
height: 130px;
border-bottom-left-radius: 100%;
border-bottom-right-radius: 100%;
}
.upgrade-title-warp {
z-index: 1;
position: relative;
.upgrade-title-warp-txt {
color: var(--next-color-white);
font-size: 22px;
letter-spacing: 3px;
}
.upgrade-title-warp-version {
color: var(--next-color-white);
background-color: var(--el-color-primary-light-4);
font-size: 12px;
position: absolute;
display: flex;
top: -2px;
right: -50px;
padding: 2px 4px;
border-radius: 2px;
}
}
}
.upgrade-content {
padding: 20px;
line-height: 22px;
.upgrade-content-desc {
color: var(--el-color-info-light-5);
font-size: 12px;
}
}
.upgrade-btn {
border-top: 1px solid var(--el-border-color-lighter, #ebeef5);
display: flex;
justify-content: space-around;
padding: 15px 20px;
.el-button {
width: 100%;
}
}
}
}
</style>

View File

@ -1,13 +1,12 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import pinia from '/@/stores/index'; import pinia from '/@/stores/index';
import App from './App.vue'; import App from '/@/App.vue';
import router from './router'; import router from '/@/router';
import { directive } from '/@/directive/index'; import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n/index'; import { i18n } from '/@/i18n/index';
import other from '/@/utils/other'; import other from '/@/utils/other';
import ElementPlus from 'element-plus'; import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss'; import '/@/theme/index.scss';
import VueGridLayout from 'vue-grid-layout'; import VueGridLayout from 'vue-grid-layout';
@ -16,4 +15,4 @@ const app = createApp(App);
directive(app); directive(app);
other.elSvg(app); other.elSvg(app);
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).mount('#app'); app.use(pinia).use(router).use(ElementPlus).use(i18n).use(VueGridLayout).mount('#app');

View File

@ -43,6 +43,9 @@ export async function initBackEndControlRoutes() {
await useUserInfo().setUserInfos(); await useUserInfo().setUserInfos();
// 获取路由菜单数据 // 获取路由菜单数据
const res = await getBackEndControlRoutes(); const res = await getBackEndControlRoutes();
// 无登录权限时,添加判断
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
if (res.data.length <= 0) return Promise.resolve(true);
// 存储接口原始路由未处理component根据需求选择使用 // 存储接口原始路由未处理component根据需求选择使用
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data))); useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
// 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由 // 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由
@ -50,7 +53,7 @@ export async function initBackEndControlRoutes() {
// 添加动态路由 // 添加动态路由
await setAddRoute(); await setAddRoute();
// 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 // 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
await setFilterMenuAndCacheTagsViewRoutes(); setFilterMenuAndCacheTagsViewRoutes();
} }
/** /**
@ -58,7 +61,7 @@ export async function initBackEndControlRoutes() {
* @description 用于左侧菜单、横向菜单的显示 * @description 用于左侧菜单、横向菜单的显示
* @description 用于 tagsView、菜单搜索中未过滤隐藏的(isHide) * @description 用于 tagsView、菜单搜索中未过滤隐藏的(isHide)
*/ */
export function setFilterMenuAndCacheTagsViewRoutes() { export async function setFilterMenuAndCacheTagsViewRoutes() {
const storesRoutesList = useRoutesList(pinia); const storesRoutesList = useRoutesList(pinia);
storesRoutesList.setRoutesList(dynamicRoutes[0].children as any); storesRoutesList.setRoutesList(dynamicRoutes[0].children as any);
setCacheTagsViewRoutes(); setCacheTagsViewRoutes();
@ -119,8 +122,8 @@ export function getBackEndControlRoutes() {
* @description 用于菜单管理界面刷新菜单(未进行测试) * @description 用于菜单管理界面刷新菜单(未进行测试)
* @description 路径:/src/views/system/menu/component/addMenu.vue * @description 路径:/src/views/system/menu/component/addMenu.vue
*/ */
export function setBackEndControlRefreshRoutes() { export async function setBackEndControlRefreshRoutes() {
getBackEndControlRoutes(); await getBackEndControlRoutes();
} }
/** /**

View File

@ -26,10 +26,13 @@ export async function initFrontEndControlRoutes() {
// 触发初始化用户信息 pinia // 触发初始化用户信息 pinia
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP // https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await useUserInfo(pinia).setUserInfos(); await useUserInfo(pinia).setUserInfos();
// 无登录权限时,添加判断
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
if (useUserInfo().userInfos.roles.length <= 0) return Promise.resolve(true);
// 添加动态路由 // 添加动态路由
await setAddRoute(); await setAddRoute();
// 设置递归过滤有权限的路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 // 设置递归过滤有权限的路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
await setFilterMenuAndCacheTagsViewRoutes(); setFilterMenuAndCacheTagsViewRoutes();
} }
/** /**

View File

@ -856,7 +856,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isAffix: false, isAffix: false,
isIframe: false, isIframe: false,
roles: ['admin'], roles: ['admin'],
icon: 'iconfont icon-diannao', icon: 'iconfont icon-wenducanshu-05',
}, },
}, },
{ {

View File

@ -23,10 +23,7 @@ export const useThemeConfig = defineStore('themeConfig', {
isIsDark: false, isIsDark: false,
/** /**
* 菜单 / 顶栏 * 顶栏设置
* 注意v1.0.17 版本去除设置布局切换重置主题样式initSetLayoutChange
* 切换布局需手动设置样式,设置的样式自动同步各布局,
* 代码位置:/@/layout/navBars/breadcrumb/setings.vue
*/ */
// 默认顶栏导航背景颜色 // 默认顶栏导航背景颜色
topBar: '#ffffff', topBar: '#ffffff',
@ -34,12 +31,22 @@ export const useThemeConfig = defineStore('themeConfig', {
topBarColor: '#606266', topBarColor: '#606266',
// 是否开启顶栏背景颜色渐变 // 是否开启顶栏背景颜色渐变
isTopBarColorGradual: false, isTopBarColorGradual: false,
/**
* 菜单设置
*/
// 默认菜单导航背景颜色 // 默认菜单导航背景颜色
menuBar: '#545c64', menuBar: '#545c64',
// 默认菜单导航字体颜色 // 默认菜单导航字体颜色
menuBarColor: '#eaeaea', menuBarColor: '#eaeaea',
// 默认菜单高亮背景色
menuBarActiveColor: 'rgba(0, 0, 0, 0.2)',
// 是否开启菜单背景颜色渐变 // 是否开启菜单背景颜色渐变
isMenuBarColorGradual: false, isMenuBarColorGradual: false,
/**
* 分栏设置
*/
// 默认分栏菜单背景颜色 // 默认分栏菜单背景颜色
columnsMenuBar: '#545c64', columnsMenuBar: '#545c64',
// 默认分栏菜单字体颜色 // 默认分栏菜单字体颜色
@ -89,7 +96,7 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启 TagsView 共用 // 是否开启 TagsView 共用
isShareTagsView: false, isShareTagsView: false,
// 是否开启 Footer 底部版权信息 // 是否开启 Footer 底部版权信息
isFooter: true, isFooter: false,
// 是否开启灰色模式 // 是否开启灰色模式
isGrayscale: false, isGrayscale: false,
// 是否开启色弱模式 // 是否开启色弱模式
@ -114,7 +121,7 @@ export const useThemeConfig = defineStore('themeConfig', {
/** /**
* 布局切换 * 布局切换
* 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/breadcrumb/setings.vue * 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/topBar/setings.vue
* 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法 * 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法
*/ */
// 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults // 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults

View File

@ -22,7 +22,7 @@ export const useUserInfo = defineStore('userInfo', {
if (Session.get('userInfo')) { if (Session.get('userInfo')) {
this.userInfos = Session.get('userInfo'); this.userInfos = Session.get('userInfo');
} else { } else {
const userInfos: any = await this.getApiUserInfo(); const userInfos = <UserInfos>await this.getApiUserInfo();
this.userInfos = userInfos; this.userInfos = userInfos;
} }
}, },
@ -63,6 +63,7 @@ export const useUserInfo = defineStore('userInfo', {
roles: defaultRoles, roles: defaultRoles,
authBtnList: defaultAuthBtnList, authBtnList: defaultAuthBtnList,
}; };
Session.set('userInfo', userInfos);
resolve(userInfos); resolve(userInfos);
}, 0); }, 0);
}); });

View File

@ -141,6 +141,8 @@ body,
.layout-aside-pc-1 { .layout-aside-pc-1 {
width: 1px !important; width: 1px !important;
transition: width 0.3s ease; transition: width 0.3s ease;
position: relative;
left: -1px;
} }
// 手机端左侧导航样式 // 手机端左侧导航样式
.layout-aside-mobile { .layout-aside-mobile {

View File

@ -22,6 +22,7 @@
--next-bg-topBarColor: var(--next-color-bar) !important; --next-bg-topBarColor: var(--next-color-bar) !important;
--next-bg-menuBar: var(--next-color-disabled) !important; --next-bg-menuBar: var(--next-color-disabled) !important;
--next-bg-menuBarColor: var(--next-color-bar) !important; --next-bg-menuBarColor: var(--next-color-bar) !important;
--next-bg-menuBarActiveColor: var(--next-color-hover-rgba) !important;
--next-bg-columnsMenuBar: var(--next-color-disabled) !important; --next-bg-columnsMenuBar: var(--next-color-disabled) !important;
--next-bg-columnsMenuBarColor: var(--next-color-bar) !important; --next-bg-columnsMenuBarColor: var(--next-color-bar) !important;
--next-border-color-light: var(--next-border-black) !important; --next-border-color-light: var(--next-border-black) !important;
@ -105,9 +106,15 @@
background-color: var(--next-color-disabled) !important; background-color: var(--next-color-disabled) !important;
} }
// topBar
.layout-navbars-breadcrumb-index {
background: none !important;
}
// menu // menu
.layout-aside { .layout-aside {
border-right: 1px solid var(--next-border-color-light) !important; border-right: 1px solid var(--next-border-color-light) !important;
@extend .layout-navbars-breadcrumb-index;
} }
// colorPicker // colorPicker

View File

@ -19,9 +19,6 @@
/* Input 输入框、InputNumber 计数器 /* Input 输入框、InputNumber 计数器
------------------------------- */ ------------------------------- */
.el-input {
height: 100%;
}
// 菜单搜索 // 菜单搜索
.el-autocomplete-suggestion__wrap { .el-autocomplete-suggestion__wrap {
max-height: 280px !important; max-height: 280px !important;
@ -71,7 +68,7 @@
------------------------------- */ ------------------------------- */
// 鼠标 hover 时颜色 // 鼠标 hover 时颜色
.el-menu-hover-bg-color { .el-menu-hover-bg-color {
background-color: var(--next-color-menu-hover) !important; background-color: var(--next-bg-menuBarActiveColor) !important;
} }
// 默认样式修改 // 默认样式修改
.el-menu { .el-menu {
@ -111,6 +108,9 @@
.el-sub-menu:not(.is-opened):hover .el-sub-menu__title { .el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
@extend .el-menu-hover-bg-color; @extend .el-menu-hover-bg-color;
} }
.el-menu-item:hover {
@extend .el-menu-hover-bg-color;
}
.el-sub-menu.is-active.is-opened .el-sub-menu__title { .el-sub-menu.is-active.is-opened .el-sub-menu__title {
background-color: unset !important; background-color: unset !important;
} }
@ -148,12 +148,12 @@
background: var(--next-bg-topBar); background: var(--next-bg-topBar);
.el-menu-item, .el-menu-item,
.el-sub-menu { .el-sub-menu {
height: 50px !important; height: 48px !important;
line-height: 50px !important; line-height: 48px !important;
color: var(--next-bg-topBarColor); color: var(--next-bg-topBarColor);
.el-sub-menu__title { .el-sub-menu__title {
height: 50px !important; height: 48px !important;
line-height: 50px !important; line-height: 48px !important;
color: var(--next-bg-topBarColor); color: var(--next-bg-topBarColor);
} }
.el-popper.is-pure.is-light { .el-popper.is-pure.is-light {
@ -180,7 +180,7 @@
width: 100% !important; width: 100% !important;
.el-menu-item, .el-menu-item,
.el-sub-menu__title { .el-sub-menu__title {
height: 50px !important; height: 48px !important;
color: var(--next-bg-topBarColor); color: var(--next-bg-topBarColor);
} }
.el-menu-item:not(.is-active):hover, .el-menu-item:not(.is-active):hover,
@ -188,6 +188,18 @@
color: var(--next-bg-topBarColor); color: var(--next-bg-topBarColor);
} }
} }
// 菜单收起时,图标不居中问题
.el-menu--collapse {
.el-menu-item .iconfont,
.el-sub-menu .iconfont,
.el-menu-item .fa,
.el-sub-menu .fa {
margin-right: 0 !important;
}
.el-sub-menu__title {
padding-right: 0 !important;
}
}
/* Tabs 标签页 /* Tabs 标签页
------------------------------- */ ------------------------------- */
@ -282,6 +294,10 @@
.layout-container-view .el-scrollbar__view { .layout-container-view .el-scrollbar__view {
height: 100%; height: 100%;
} }
/*防止分栏布局二级菜单很多时,滚动条消失问题*/
.layout-columns-warp .layout-aside .el-scrollbar__view {
height: unset !important;
}
/* Pagination 分页 /* Pagination 分页
------------------------------- */ ------------------------------- */

View File

@ -5,64 +5,25 @@
.icon-selector-warp { .icon-selector-warp {
height: 260px; height: 260px;
overflow: hidden; overflow: hidden;
position: relative;
.icon-selector-warp-title { .icon-selector-warp-title {
position: absolute;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
padding: 0 15px; left: 15px;
.icon-selector-warp-title-tab {
span {
cursor: pointer;
&:hover {
color: var(--el-color-primary);
text-decoration: underline;
}
}
.span-active {
color: var(--el-color-primary);
text-decoration: underline;
}
}
} }
.icon-selector-warp-row { .el-tabs__header {
height: 230px; display: flex;
overflow: hidden; justify-content: flex-end;
border-top: 1px solid var(--el-border-color); padding: 0 15px;
.el-row { border-bottom: 1px solid var(--el-border-color-light);
padding: 15px; margin: 0 !important;
} .el-tabs__nav-wrap {
.el-scrollbar__bar.is-horizontal { &::after {
display: none; height: 0 !important;
}
.icon-selector-warp-item {
display: flex;
border: 1px solid var(--el-border-color);
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
.icon-selector-warp-item-value {
i {
font-size: 20px;
color: var(--el-text-color-regular);
}
} }
&:hover { .el-tabs__item {
cursor: pointer; padding: 0 5px !important;
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
.icon-selector-warp-item-value {
i {
color: var(--el-color-primary);
}
}
}
}
.icon-selector-active {
background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-5);
.icon-selector-warp-item-value {
i {
color: var(--el-color-primary);
}
} }
} }
} }

View File

@ -1,8 +1,8 @@
@import 'element-plus/dist/index.css';
@import './app.scss'; @import './app.scss';
@import 'common/transition.scss'; @import 'common/transition.scss';
@import './other.scss'; @import './other.scss';
@import './element.scss'; @import './element.scss';
@import './iconSelector.scss';
@import './media/media.scss'; @import './media/media.scss';
@import './waves.scss'; @import './waves.scss';
@import './dark.scss'; @import './dark.scss';

View File

@ -16,4 +16,16 @@
// 响应式表单时,登录页需要重新处理 // 响应式表单时,登录页需要重新处理
display: unset !important; display: unset !important;
} }
// 表格演示中的表单筛选
.table-form-btn {
display: flex !important;
.el-form-item__label {
width: auto !important;
}
}
// 表格演示中的表单筛选最大高度,适配移动端
.table-search-container {
max-height: 160px;
overflow: auto;
}
} }

View File

@ -7,6 +7,10 @@
.el-message-box { .el-message-box {
width: 80% !important; width: 80% !important;
} }
// 锁屏页
.layout-lock-screen-date-top {
display: none;
}
} }
/* 页面宽度小于768px /* 页面宽度小于768px

View File

@ -8,18 +8,20 @@
height: 40px; height: 40px;
align-items: center; align-items: center;
} }
.tool-item { .tool-sortable {
display: flex; max-height: 303px;
box-sizing: border-box; .tool-sortable-item {
color: var(--el-text-color-primary); display: flex;
height: 35px; box-sizing: border-box;
align-items: center; color: var(--el-text-color-primary);
padding: var(--el-popover-padding); align-items: center;
&:hover { padding: 0 12px;
background: var(--el-fill-color-lighter); &:hover {
} background: var(--el-fill-color-lighter);
i { }
opacity: 0.7; i {
opacity: 0.7;
}
} }
} }
} }

View File

@ -5,6 +5,11 @@ declare module 'splitpanes';
declare module 'js-cookie'; declare module 'js-cookie';
declare module '@wangeditor/editor-for-vue'; declare module '@wangeditor/editor-for-vue';
declare module 'js-table2excel'; declare module 'js-table2excel';
declare module 'qs';
declare module 'sortablejs';
declare module 'element-plus/dist/locale/en';
declare module 'element-plus/dist/locale/zh-cn';
declare module 'element-plus/dist/locale/zh-tw';
// 声明一个模块,防止引入文件时报错 // 声明一个模块,防止引入文件时报错
declare module '*.json'; declare module '*.json';
@ -25,6 +30,8 @@ declare module '*.vue' {
/* eslint-disable */ /* eslint-disable */
declare interface Window { declare interface Window {
nextLoading: boolean; nextLoading: boolean;
BMAP_SATELLITE_MAP: any;
BMap: any;
} }
// 声明路由当前项类型 // 声明路由当前项类型

20
src/types/pinia.d.ts vendored
View File

@ -3,15 +3,16 @@
*/ */
// 用户信息 // 用户信息
declare interface UserInfosState<T = any> { declare interface UserInfos<T = any> {
userInfos: { authBtnList: string[];
authBtnList: string[]; photo: string;
photo: string; roles: string[];
roles: string[]; time: number;
time: number; userName: string;
userName: string; [key: string]: T;
[key: string]: T; }
}; declare interface UserInfosState {
userInfos: UserInfos;
} }
// 路由缓存列表 // 路由缓存列表
@ -48,6 +49,7 @@ declare interface ThemeConfigState {
isTopBarColorGradual: boolean; isTopBarColorGradual: boolean;
menuBar: string; menuBar: string;
menuBarColor: string; menuBarColor: string;
menuBarActiveColor: string;
isMenuBarColorGradual: boolean; isMenuBarColorGradual: boolean;
columnsMenuBar: string; columnsMenuBar: string;
columnsMenuBarColor: string; columnsMenuBarColor: string;

16
src/types/views.d.ts vendored
View File

@ -43,7 +43,6 @@ declare type Demo2State<T = any> = {
earth3DBtnList: T[]; earth3DBtnList: T[];
chartData4List: T[]; chartData4List: T[];
myCharts: T[]; myCharts: T[];
the3DEarth: HTMLDivElement | null;
}; };
/** /**
@ -243,8 +242,6 @@ declare type XyState = {
}; };
declare type WorkflowState<T = any> = { declare type WorkflowState<T = any> = {
workflowRightRef: HTMLDivElement | null;
leftNavRefs: HTMLElement[];
leftNavList: T[]; leftNavList: T[];
dropdownNode: XyState; dropdownNode: XyState;
dropdownLine: XyState; dropdownLine: XyState;
@ -305,11 +302,19 @@ declare type TableHeaderType = {
isCheck: boolean; isCheck: boolean;
}; };
declare type TableSearchType = {
label: string;
prop: string;
placeholder: string;
required: boolean;
type: string;
options?: SelectOptionType[];
};
declare type TableDemoState = { declare type TableDemoState = {
tableData: { tableData: {
data: EmptyObjectType[]; data: EmptyObjectType[];
header: TableHeaderType[]; header: TableHeaderType[];
param: EmptyObjectType;
config: { config: {
total: number; total: number;
loading: boolean; loading: boolean;
@ -318,5 +323,8 @@ declare type TableDemoState = {
isSerialNo: boolean; isSerialNo: boolean;
isOperate: boolean; isOperate: boolean;
}; };
search: TableSearchType[];
param: EmptyObjectType;
printName: string;
}; };
}; };

125
src/utils/build.ts Normal file
View File

@ -0,0 +1,125 @@
import importToCDN from 'vite-plugin-cdn-import';
/**
* 打包相关
* 注意 prodUrl使用的是 jsdelivr 还是 unpkg。它们的 path 可能不一致
* 文章链接https://blog.csdn.net/qq_34450741/article/details/129766676使用的是 jsdelivr
* @description importToCDN https://github.com/mmf-fe/vite-plugin-cdn-import
* @description cdn 在线引入的 cdn 地址配置。pathhttps://www.jsdelivr.com/ || https://unpkg.com/
* @description external 打包时过滤包导入。参考https://rollupjs.org/configuration-options/#external
*/
export const buildConfig = {
cdn() {
return importToCDN({
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [
// autoComplete('vue'),
// autoComplete('axios'),
{
name: 'vue',
var: 'Vue',
path: 'dist/vue.global.js',
},
{
name: 'vue-demi',
var: 'VueDemi',
path: 'lib/index.iife.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.global.js',
},
{
name: 'element-plus',
var: 'ElementPlus',
path: 'dist/index.full.js',
},
// {
// name: '@element-plus/icons-vue',
// var: 'ElementPlusIconsVue',
// path: 'dist/index.iife.min.js',
// },
// {
// name: 'echarts',
// var: 'echarts',
// path: 'dist/echarts.min.js',
// },
// {
// name: 'echarts-gl',
// var: 'echarts-gl',
// path: 'dist/echarts-gl.min.js',
// },
// {
// name: 'echarts-wordcloud',
// var: 'echarts-wordcloud',
// path: 'dist/echarts-wordcloud.min.js',
// },
// {
// name: 'vue-i18n',
// var: 'VueI18n',
// path: 'dist/vue-i18n.global.min.js',
// },
// {
// name: 'jsplumb',
// var: 'jsPlumb',
// path: 'dist/js/jsplumb.min.js',
// },
// {
// name: 'cropperjs',
// var: 'Cropper',
// path: 'dist/cropper.min.js',
// },
// {
// name: 'sortablejs',
// var: 'Sortable',
// path: 'Sortable.min.js',
// },
// {
// name: 'qrcodejs2-fixes',
// var: 'QRCode',
// path: 'qrcode.min.js',
// },
// {
// name: 'print-js',
// var: 'printJS',
// path: 'dist/print.min.js',
// },
// {
// name: '@wangeditor/editor',
// var: 'wangEditor',
// path: 'dist/index.min.js',
// },
// {
// name: '@wangeditor/editor-for-vue',
// var: 'WangEditorForVue',
// path: 'dist/index.min.js',
// },
// {
// name: 'vue-grid-layout',
// var: 'VueGridLayout',
// path: 'https://cdn.jsdelivr.net/npm/vue-grid-layout@3.0.0-beta1/dist/vue-grid-layout.umd.min.js',
// },
],
});
},
external: [
'vue',
// 'axios',
'vue-router',
'element-plus',
// '@element-plus/icons-vue',
// 'echarts',
// 'echarts-gl',
// 'echarts-wordcloud',
// 'vue-i18n',
// 'jsplumb',
// 'cropperjs',
// 'sortablejs',
// 'qrcodejs2-fixes',
// 'print-js',
// '@wangeditor/editor',
// '@wangeditor/editor-for-vue',
// 'vue-grid-layout',
],
};

View File

@ -51,7 +51,7 @@ const getAwesomeIconfont = () => {
let sheetsList = []; let sheetsList = [];
let sheetsIconList = []; let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) { for (let i = 0; i < styles.length; i++) {
if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) { if (styles[i].href && styles[i].href.indexOf('https://cdn.jsdelivr.net') > -1) {
sheetsList.push(styles[i]); sheetsList.push(styles[i]);
} }
} }

View File

@ -53,8 +53,11 @@ export function useTitle() {
export function setTagsViewNameI18n(item: any) { export function setTagsViewNameI18n(item: any) {
let tagsViewName: string = ''; let tagsViewName: string = '';
const { query, params, meta } = item; const { query, params, meta } = item;
// 修复tagsViewName匹配到其他含下列单词的路由
// https://gitee.com/lyt-top/vue-next-admin/pulls/44/files
const pattern = /^\{("(zh-cn|en|zh-tw)":"[^,]+",?){1,3}}$/;
if (query?.tagsViewName || params?.tagsViewName) { if (query?.tagsViewName || params?.tagsViewName) {
if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/zh-cn|en|zh-tw\//.test(params?.tagsViewName)) { if (pattern.test(query?.tagsViewName) || pattern.test(params?.tagsViewName)) {
// 国际化 // 国际化
const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName)); const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
tagsViewName = urlTagsParams[i18n.global.locale.value]; tagsViewName = urlTagsParams[i18n.global.locale.value];

View File

@ -1,17 +1,23 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import qs from 'qs';
// 配置新建一个 axios 实例 // 配置新建一个 axios 实例
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL, baseURL: import.meta.env.VITE_API_URL,
timeout: 50000, timeout: 50000,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
paramsSerializer: {
serialize(params) {
return qs.stringify(params, { allowDots: true });
},
},
}); });
// 添加请求拦截器 // 添加请求拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: AxiosRequestConfig) => { (config) => {
// 在发送请求之前做些什么 token // 在发送请求之前做些什么 token
if (Session.get('token')) { if (Session.get('token')) {
config.headers!['Authorization'] = `${Session.get('token')}`; config.headers!['Authorization'] = `${Session.get('token')}`;
@ -40,7 +46,7 @@ service.interceptors.response.use(
} }
return Promise.reject(service.interceptors.response); return Promise.reject(service.interceptors.response);
} else { } else {
return response.data; return res;
} }
}, },
(error) => { (error) => {

View File

@ -1,7 +1,7 @@
// 字体图标 url // 字体图标 url
const cssCdnUrlList: Array<string> = [ const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/c/font_2298093_rnp72ifj3ba.css', '//at.alicdn.com/t/c/font_2298093_rnp72ifj3ba.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css', 'https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css',
]; ];
// 第三方 js url // 第三方 js url
const jsCdnUrlList: Array<string> = []; const jsCdnUrlList: Array<string> = [];

View File

@ -8,18 +8,23 @@ import Cookies from 'js-cookie';
* @method clear 移除全部永久缓存 * @method clear 移除全部永久缓存
*/ */
export const Local = { export const Local = {
// 查看 v2.4.3版本更新日志
setKey(key: string) {
// @ts-ignore
return `${__NEXT_NAME__}:${key}`;
},
// 设置永久缓存 // 设置永久缓存
set(key: string, val: any) { set<T>(key: string, val: T) {
window.localStorage.setItem(key, JSON.stringify(val)); window.localStorage.setItem(Local.setKey(key), JSON.stringify(val));
}, },
// 获取永久缓存 // 获取永久缓存
get(key: string) { get(key: string) {
let json = <string>window.localStorage.getItem(key); let json = <string>window.localStorage.getItem(Local.setKey(key));
return JSON.parse(json); return JSON.parse(json);
}, },
// 移除永久缓存 // 移除永久缓存
remove(key: string) { remove(key: string) {
window.localStorage.removeItem(key); window.localStorage.removeItem(Local.setKey(key));
}, },
// 移除全部永久缓存 // 移除全部永久缓存
clear() { clear() {
@ -36,20 +41,20 @@ export const Local = {
*/ */
export const Session = { export const Session = {
// 设置临时缓存 // 设置临时缓存
set(key: string, val: any) { set<T>(key: string, val: T) {
if (key === 'token') return Cookies.set(key, val); if (key === 'token') return Cookies.set(key, val);
window.sessionStorage.setItem(key, JSON.stringify(val)); window.sessionStorage.setItem(Local.setKey(key), JSON.stringify(val));
}, },
// 获取临时缓存 // 获取临时缓存
get(key: string) { get(key: string) {
if (key === 'token') return Cookies.get(key); if (key === 'token') return Cookies.get(key);
let json = <string>window.sessionStorage.getItem(key); let json = <string>window.sessionStorage.getItem(Local.setKey(key));
return JSON.parse(json); return JSON.parse(json);
}, },
// 移除临时缓存 // 移除临时缓存
remove(key: string) { remove(key: string) {
if (key === 'token') return Cookies.remove(key); if (key === 'token') return Cookies.remove(key);
window.sessionStorage.removeItem(key); window.sessionStorage.removeItem(Local.setKey(key));
}, },
// 移除全部临时缓存 // 移除全部临时缓存
clear() { clear() {

View File

@ -13,9 +13,7 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<img <img src="https://i.hd-r.cn/2cf0d2e192660eec23eb9d0655753e7d.png" />
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> </div>
</div> </div>

View File

@ -13,9 +13,7 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<img <img src="https://i.hd-r.cn/1a0d90a6c1e8b0184c7299dda713effd.png" />
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> </div>
</div> </div>
@ -28,6 +26,7 @@ import { useRouter } from 'vue-router';
// 定义变量内容 // 定义变量内容
const router = useRouter(); const router = useRouter();
// 返回首页
const onGoHome = () => { const onGoHome = () => {
router.push('/'); router.push('/');
}; };

View File

@ -1,18 +1,19 @@
<template> <template>
<div class="layout-padding"> <div class="layout-padding">
<div class="layout-padding-auto layout-padding-view"> <div class="layout-padding-auto layout-padding-view">
<div ref="echartsMap" style="height: 100%"></div> <div ref="echartsMapRef" style="height: 100%"></div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="funEchartsMap"> <script setup lang="ts" name="funEchartsMap">
import { reactive, onMounted } from 'vue'; import { reactive, onMounted, ref } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import 'echarts/extension/bmap/bmap'; import 'echarts/extension/bmap/bmap';
import { echartsMapList, echartsMapData } from './mock'; import { echartsMapList, echartsMapData } from './mock';
// 定义变量内容 // 定义变量内容
const echartsMapRef = ref<RefType>('');
const state = reactive({ const state = reactive({
echartsMap: '' as unknown, echartsMap: '' as unknown,
echartsMapList, echartsMapList,
@ -35,7 +36,7 @@ const convertData = (data: EmptyObjectType[]) => {
}; };
// 初始化 echartsMap // 初始化 echartsMap
const initEchartsMap = () => { const initEchartsMap = () => {
const myChart = echarts.init(<HTMLElement>state.echartsMap); const myChart = echarts.init(echartsMapRef.value);
const option = { const option = {
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',

View File

@ -66,7 +66,7 @@
</template> </template>
<script setup lang="ts" name="home"> <script setup lang="ts" name="home">
import { reactive, onMounted, ref, watch, nextTick, onActivated } from 'vue'; import { reactive, onMounted, ref, watch, nextTick, onActivated, markRaw } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
@ -192,7 +192,7 @@ const state = reactive({
// 折线图 // 折线图
const initLineChart = () => { const initLineChart = () => {
if (!state.global.dispose.some((b: any) => b === state.global.homeChartOne)) state.global.homeChartOne.dispose(); if (!state.global.dispose.some((b: any) => b === state.global.homeChartOne)) state.global.homeChartOne.dispose();
state.global.homeChartOne = echarts.init(homeLineRef.value, state.charts.theme); state.global.homeChartOne = markRaw(echarts.init(homeLineRef.value, state.charts.theme));
const option = { const option = {
backgroundColor: state.charts.bgColor, backgroundColor: state.charts.bgColor,
title: { title: {
@ -274,7 +274,7 @@ const initLineChart = () => {
// 饼图 // 饼图
const initPieChart = () => { const initPieChart = () => {
if (!state.global.dispose.some((b: any) => b === state.global.homeChartTwo)) state.global.homeChartTwo.dispose(); if (!state.global.dispose.some((b: any) => b === state.global.homeChartTwo)) state.global.homeChartTwo.dispose();
state.global.homeChartTwo = echarts.init(homePieRef.value, state.charts.theme); state.global.homeChartTwo = markRaw(echarts.init(homePieRef.value, state.charts.theme));
var getname = ['房屋及结构物', '专用设备', '通用设备', '文物和陈列品', '图书、档案']; var getname = ['房屋及结构物', '专用设备', '通用设备', '文物和陈列品', '图书、档案'];
var getvalue = [34.2, 38.87, 17.88, 9.05, 2.05]; var getvalue = [34.2, 38.87, 17.88, 9.05, 2.05];
var data = []; var data = [];
@ -359,7 +359,7 @@ const initPieChart = () => {
// 柱状图 // 柱状图
const initBarChart = () => { const initBarChart = () => {
if (!state.global.dispose.some((b: any) => b === state.global.homeCharThree)) state.global.homeCharThree.dispose(); if (!state.global.dispose.some((b: any) => b === state.global.homeCharThree)) state.global.homeCharThree.dispose();
state.global.homeCharThree = echarts.init(homeBarRef.value, state.charts.theme); state.global.homeCharThree = markRaw(echarts.init(homeBarRef.value, state.charts.theme));
const option = { const option = {
backgroundColor: state.charts.bgColor, backgroundColor: state.charts.bgColor,
title: { title: {

View File

@ -27,6 +27,7 @@ import { onMounted, ref } from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
import { Session } from '/@/utils/storage';
import { frontEndsResetRoute, setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/frontEnd'; import { frontEndsResetRoute, setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/frontEnd';
// 定义变量内容 // 定义变量内容
@ -40,6 +41,9 @@ const initUserAuth = () => {
}; };
// 用户权限改变时 // 用户权限改变时
const onRadioChange = async () => { const onRadioChange = async () => {
// 清空之前缓存的 userInfo防止不请求接口。
// stores/userInfo.ts
Session.remove('userInfo');
// 模拟数据 // 模拟数据
frontEndsResetRoute(); frontEndsResetRoute();
Cookies.set('userName', userAuth.value); Cookies.set('userName', userAuth.value);

View File

@ -100,37 +100,41 @@ const onSignIn = async () => {
Cookies.set('userName', state.ruleForm.userName); Cookies.set('userName', state.ruleForm.userName);
if (!themeConfig.value.isRequestRoutes) { if (!themeConfig.value.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序 // 前端控制路由2、请注意执行顺序
await initFrontEndControlRoutes(); const isNoPower = await initFrontEndControlRoutes();
signInSuccess(); signInSuccess(isNoPower);
} else { } else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由 // 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/" // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
await initBackEndControlRoutes(); const isNoPower = await initBackEndControlRoutes();
// 执行完 initBackEndControlRoutes再执行 signInSuccess // 执行完 initBackEndControlRoutes再执行 signInSuccess
signInSuccess(); signInSuccess(isNoPower);
} }
}; };
// 登录成功后的跳转 // 登录成功后的跳转
const signInSuccess = () => { const signInSuccess = (isNoPower: boolean | undefined) => {
// 初始化登录成功时间问候语 if (isNoPower) {
let currentTimeInfo = currentTime.value; ElMessage.warning('抱歉,您没有登录权限');
// 登录成功,跳到转首页 Session.clear();
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: <string>route.query?.redirect,
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
});
} else { } else {
router.push('/'); // 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: <string>route.query?.redirect,
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
const signInText = t('message.signInText');
ElMessage.success(`${currentTimeInfo}${signInText}`);
// 添加 loading防止第一次进入界面时出现短暂空白
NextLoading.start();
} }
// 登录成功提示 state.loading.signIn = false;
// 关闭 loading
state.loading.signIn = true;
const signInText = t('message.signInText');
ElMessage.success(`${currentTimeInfo}${signInText}`);
// 添加 loading防止第一次进入界面时出现短暂空白
NextLoading.start();
}; };
</script> </script>

View File

@ -35,6 +35,13 @@ const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelect
const state = reactive({ const state = reactive({
modelIcon: '', modelIcon: '',
tableData: [ tableData: [
{
a1: 'v-model',
a2: '双向绑定值',
a3: 'string',
a4: '',
a5: '',
},
{ {
a1: 'prepend', a1: 'prepend',
a2: '输入框前置内容,只能字体图标', a2: '输入框前置内容,只能字体图标',
@ -63,13 +70,6 @@ const state = reactive({
a4: '', a4: '',
a5: '请选择图标', a5: '请选择图标',
}, },
{
a1: 'type',
a2: 'icon 图标类型',
a3: 'string',
a4: 'ali / ele / awe / all',
a5: 'ele',
},
{ {
a1: 'disabled', a1: 'disabled',
a2: '禁用', a2: '禁用',

View File

@ -1,15 +1,7 @@
<template> <template>
<div class="table-demo-container layout-padding"> <div class="table-demo-container layout-padding">
<div class="table-demo-padding layout-padding-view layout-padding-auto"> <div class="table-demo-padding layout-padding-view layout-padding-auto">
<div class="system-user-search mb15"> <TableSearch :search="state.tableData.search" @search="onSearch" />
<el-input v-model="state.tableData.param.search" size="default" placeholder="请输入核酸采样点名称" style="max-width: 180px" />
<el-button size="default" type="primary" class="ml10" @click="onSearch">
<el-icon>
<ele-Search />
</el-icon>
查询
</el-button>
</div>
<Table <Table
ref="tableRef" ref="tableRef"
v-bind="state.tableData" v-bind="state.tableData"
@ -28,6 +20,7 @@ import { ElMessage } from 'element-plus';
// 引入组件 // 引入组件
const Table = defineAsyncComponent(() => import('/@/components/table/index.vue')); const Table = defineAsyncComponent(() => import('/@/components/table/index.vue'));
const TableSearch = defineAsyncComponent(() => import('/@/views/make/tableDemo/search.vue'));
// 定义变量内容 // 定义变量内容
const tableRef = ref<RefType>(); const tableRef = ref<RefType>();
@ -44,10 +37,6 @@ const state = reactive<TableDemoState>({
{ key: 'isSupport', colWidth: '', title: '是否支持24小时核酸检测', type: 'text', isCheck: true }, { key: 'isSupport', colWidth: '', title: '是否支持24小时核酸检测', type: 'text', isCheck: true },
{ key: 'image', colWidth: '', width: '70', height: '40', title: '图片描述', type: 'image', isCheck: true }, { key: 'image', colWidth: '', width: '70', height: '40', title: '图片描述', type: 'image', isCheck: true },
], ],
// 搜索参数(可选)
param: {
search: '',
},
// 配置项(必传) // 配置项(必传)
config: { config: {
total: 0, // 列表总数 total: 0, // 列表总数
@ -57,6 +46,33 @@ const state = reactive<TableDemoState>({
isSelection: true, // 是否显示表格多选 isSelection: true, // 是否显示表格多选
isOperate: true, // 是否显示表格操作栏 isOperate: true, // 是否显示表格操作栏
}, },
// 搜索表单,动态生成(传空数组时,将不显示搜索,注意格式)
search: [
{ label: '采样点名称', prop: 'name', placeholder: '请输入应检尽检核酸采样点名称', required: true, type: 'input' },
{ label: '详细地址', prop: 'address', placeholder: '请输入详细地址', required: false, type: 'input' },
{ label: '联系电话', prop: 'phone', placeholder: '请输入采样点联系电话', required: false, type: 'input' },
{ label: '开放时间', prop: 'time', placeholder: '请选择', required: false, type: 'date' },
{
label: '支持24小时',
prop: 'isSupport',
placeholder: '请选择',
required: false,
type: 'select',
options: [
{ label: '是', value: 1 },
{ label: '否', value: 0 },
],
},
{ label: '图片描述', prop: 'image', placeholder: '请输入图片描述', required: false, type: 'input' },
{ label: '核酸机构', prop: 'mechanism', placeholder: '请输入核酸机构', required: false, type: 'input' },
],
// 搜索参数(不用传,用于分页、搜索时传给后台的值,`getTableData` 中使用)
param: {
pageNum: 1,
pageSize: 10,
},
// 打印标题
printName: 'vueNextAdmin 表格打印演示',
}, },
}); });
@ -81,8 +97,9 @@ const getTableData = () => {
state.tableData.config.loading = false; state.tableData.config.loading = false;
}, 500); }, 500);
}; };
// 搜索 // 搜索点击时表单回调
const onSearch = () => { const onSearch = (data: EmptyObjectType) => {
state.tableData.param = Object.assign({}, state.tableData.param, { ...data });
tableRef.value.pageReset(); tableRef.value.pageReset();
}; };
// 删除当前项回调 // 删除当前项回调
@ -100,7 +117,6 @@ const onTablePageChange = (page: TableDemoPageType) => {
const onSortHeader = (data: TableHeaderType[]) => { const onSortHeader = (data: TableHeaderType[]) => {
state.tableData.header = data; state.tableData.header = data;
}; };
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
getTableData(); getTableData();

View File

@ -0,0 +1,110 @@
<template>
<div class="table-search-container" v-if="props.search.length > 0">
<el-form ref="tableSearchRef" :model="state.form" size="default" label-width="100px" class="table-form">
<el-row>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20" v-for="(val, key) in search" :key="key" v-show="key === 0 || state.isToggle">
<template v-if="val.type !== ''">
<el-form-item
:label="val.label"
:prop="val.prop"
:rules="[{ required: val.required, message: `${val.label}不能为空`, trigger: val.type === 'input' ? 'blur' : 'change' }]"
>
<el-input v-model="state.form[val.prop]" :placeholder="val.placeholder" clearable v-if="val.type === 'input'" style="width: 100%" />
<el-date-picker
v-model="state.form[val.prop]"
type="date"
:placeholder="val.placeholder"
v-else-if="val.type === 'date'"
style="width: 100%"
/>
<el-select v-model="state.form[val.prop]" :placeholder="val.placeholder" v-else-if="val.type === 'select'" style="width: 100%">
<el-option v-for="item in val.options" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</template>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item class="table-form-btn" :label-width="search.length <= 1 ? '10px' : '100px'">
<template #label v-if="search.length > 1">
<div class="table-form-btn-toggle ml10" @click="state.isToggle = !state.isToggle">
<span>{{ state.isToggle ? '收筛选' : '展筛选' }}</span>
<SvgIcon :name="state.isToggle ? 'ele-ArrowUp' : 'ele-ArrowDown'" />
</div>
</template>
<div>
<el-button size="default" type="primary" @click="onSearch(tableSearchRef)">查询 </el-button>
<el-button size="default" type="info" class="ml10" @click="onReset(tableSearchRef)"> 重置 </el-button>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts" name="makeTableDemoSearch">
import { reactive, ref, onMounted } from 'vue';
import type { FormInstance } from 'element-plus';
// 定义父组件传过来的值
const props = defineProps({
// 搜索表单
search: {
type: Array<TableSearchType>,
default: () => [],
},
});
// 定义子组件向父组件传值/事件
const emit = defineEmits(['search']);
// 定义变量内容
const tableSearchRef = ref<FormInstance>();
const state = reactive({
form: {},
isToggle: false,
});
// 查询
const onSearch = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
emit('search', state.form);
} else {
return false;
}
});
};
// 重置
const onReset = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
emit('search', state.form);
};
// 初始化 form 字段,取自父组件 search.prop
const initFormField = () => {
if (props.search.length <= 0) return false;
props.search.forEach((v) => (state.form[v.prop] = ''));
};
// 页面加载时
onMounted(() => {
initFormField();
});
</script>
<style scoped lang="scss">
.table-search-container {
display: flex;
.table-form {
flex: 1;
.table-form-btn-toggle {
white-space: nowrap;
user-select: none;
display: flex;
align-items: center;
color: var(--el-color-primary);
}
}
}
</style>

View File

@ -8,21 +8,12 @@
element-loading-background="rgba(255, 255, 255, 0.1)" element-loading-background="rgba(255, 255, 255, 0.1)"
:class="{ 'min-h-360': state.tableData.data.length <= 0 }" :class="{ 'min-h-360': state.tableData.data.length <= 0 }"
> >
<div <div v-for="(val, key) in state.filtering" :key="key" ref="dlRefs" class="filtering-list-flex">
v-for="(val, key) in filtering"
:key="key"
:ref="
(el) => {
if (el) dlRefs[key] = el;
}
"
class="filtering-list-flex"
>
<div class="filtering-list-title">{{ val.title }}</div> <div class="filtering-list-title">{{ val.title }}</div>
<div class="filtering-list-item" :style="{ height: val.isMore ? 'auto' : '50px' }"> <div class="filtering-list-item" :style="{ height: val.isMore ? 'auto' : '50px' }">
<span class="span" :class="v.active ? 'dd-active' : ''" v-for="(v, k) in val.children" :key="k" @click="onSelItem(val, v)">{{ <span class="span" :class="v.active ? 'dd-active' : ''" v-for="(v, k) in val.children" :key="k" @click="onSelItem(val, v)">
v.label {{ v.label }}
}}</span> </span>
<div class="dd-more" v-if="val.isShowMore" @click="val.isMore = !val.isMore"> <div class="dd-more" v-if="val.isShowMore" @click="val.isMore = !val.isMore">
<span>{{ val.isMore ? '收起' : '展开' }}</span> <span>{{ val.isMore ? '收起' : '展开' }}</span>
<i :class="val.isMore ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i> <i :class="val.isMore ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
@ -235,6 +226,9 @@ const onHandleCurrentChange = (val: number) => {
flex-wrap: wrap; flex-wrap: wrap;
align-content: flex-start; align-content: flex-start;
margin: 0 -5px; margin: 0 -5px;
.el-row {
width: 101%;
}
.flex-warp-item { .flex-warp-item {
padding: 5px; padding: 5px;
width: 100%; width: 100%;

View File

@ -99,6 +99,9 @@ onMounted(() => {
flex-wrap: wrap; flex-wrap: wrap;
align-content: flex-start; align-content: flex-start;
margin: 0 -5px; margin: 0 -5px;
.el-row {
width: 101%;
}
.flex-warp-item { .flex-warp-item {
padding: 5px; padding: 5px;
width: 100%; width: 100%;

View File

@ -93,6 +93,9 @@ const onHandleCurrentChange = (val: number) => {
flex-wrap: wrap; flex-wrap: wrap;
align-content: flex-start; align-content: flex-start;
margin: 0 -5px; margin: 0 -5px;
.el-row {
width: 101%;
}
.flex-warp-item { .flex-warp-item {
padding: 5px; padding: 5px;
width: 100%; width: 100%;

View File

@ -2,7 +2,7 @@
<div class="layout-pd"> <div class="layout-pd">
<el-card shadow="hover" header="表单表格验证"> <el-card shadow="hover" header="表单表格验证">
<el-form ref="tableRulesRef" :model="state.tableData" size="default"> <el-form ref="tableRulesRef" :model="state.tableData" size="default">
<el-table :data="state.tableData.data" border class="module-table-uncollected"> <el-table ref="tableRef" :data="state.tableData.data" border class="module-table-uncollected" max-height="200">
<el-table-column <el-table-column
v-for="(item, index) in state.tableData.header" v-for="(item, index) in state.tableData.header"
:key="index" :key="index"
@ -55,12 +55,13 @@
</template> </template>
<script setup lang="ts" name="pagesTableRules"> <script setup lang="ts" name="pagesTableRules">
import { reactive, ref } from 'vue'; import { reactive, ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import type { FormInstance } from 'element-plus'; import type { FormInstance } from 'element-plus';
// 定义变量内容 // 定义变量内容
const tableRulesRef = ref<FormInstance>(); const tableRulesRef = ref<FormInstance>();
const tableRef = ref();
const state = reactive<TableRulesState>({ const state = reactive<TableRulesState>({
tableData: { tableData: {
data: [], data: [],
@ -103,5 +104,9 @@ const onAddRow = () => {
a7: '', a7: '',
a8: '', a8: '',
}); });
tableRef.value.doLayout();
nextTick(() => {
tableRef.value.setScrollTop(1000000);
});
}; };
</script> </script>

View File

@ -15,8 +15,8 @@
<span class="tree-custom-node"> <span class="tree-custom-node">
<span style="flex: 1">{{ node.label }}</span> <span style="flex: 1">{{ node.label }}</span>
<span v-if="data.isShow" style="flex: 1; display: flex"> <span v-if="data.isShow" style="flex: 1; display: flex">
<span type="text" size="default" style="flex: 1">{{ data.label1 }}</span> <span style="flex: 1">{{ data.label1 }}</span>
<span type="text" size="default" style="flex: 1">{{ data.label2 }}</span> <span style="flex: 1">{{ data.label2 }}</span>
</span> </span>
</span> </span>
</template> </template>

View File

@ -222,7 +222,7 @@ defineExpose({
<style scoped lang="scss"> <style scoped lang="scss">
.workflow-drawer-node { .workflow-drawer-node {
:deep { :deep() {
.el-tabs { .el-tabs {
box-shadow: unset; box-shadow: unset;
border: unset; border: unset;

View File

@ -12,7 +12,7 @@
<el-scrollbar> <el-scrollbar>
<div <div
ref="leftNavRefs" ref="leftNavRefs"
v-for="val in leftNavList" v-for="val in state.leftNavList"
:key="val.id" :key="val.id"
:style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }" :style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
class="workflow-left-id" class="workflow-left-id"
@ -86,6 +86,8 @@ const Drawer = defineAsyncComponent(() => import('./component/drawer/index.vue')
const Help = defineAsyncComponent(() => import('./component/tool/help.vue')); const Help = defineAsyncComponent(() => import('./component/tool/help.vue'));
// 定义变量内容 // 定义变量内容
const leftNavRefs = ref([]);
const workflowRightRef = ref();
const contextmenuNodeRef = ref(); const contextmenuNodeRef = ref();
const contextmenuLineRef = ref(); const contextmenuLineRef = ref();
const drawerRef = ref(); const drawerRef = ref();
@ -95,8 +97,6 @@ const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig); const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction(); const { copyText } = commonFunction();
const state = reactive<WorkflowState>({ const state = reactive<WorkflowState>({
workflowRightRef: null,
leftNavRefs: [],
leftNavList: [], leftNavList: [],
dropdownNode: { x: '', y: '' }, dropdownNode: { x: '', y: '' },
dropdownLine: { x: '', y: '' }, dropdownLine: { x: '', y: '' },
@ -151,7 +151,7 @@ const initLeftNavList = () => {
}; };
// 左侧导航-初始化拖动 // 左侧导航-初始化拖动
const initSortable = () => { const initSortable = () => {
state.leftNavRefs.forEach((v) => { leftNavRefs.value.forEach((v) => {
Sortable.create(v as HTMLDivElement, { Sortable.create(v as HTMLDivElement, {
group: { group: {
name: 'vue-next-admin-1', name: 'vue-next-admin-1',
@ -165,7 +165,7 @@ const initSortable = () => {
onEnd: function (evt: any) { onEnd: function (evt: any) {
const { name, icon, id } = evt.clone.dataset; const { name, icon, id } = evt.clone.dataset;
const { layerX, layerY, clientX, clientY } = evt.originalEvent; const { layerX, layerY, clientX, clientY } = evt.originalEvent;
const el = state.workflowRightRef!; const el = workflowRightRef.value!;
const { x, y, width, height } = el.getBoundingClientRect(); const { x, y, width, height } = el.getBoundingClientRect();
if (clientX < x || clientX > width + x || clientY < y || y > y + height) { if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
ElMessage.warning('请把节点拖入到画布中'); ElMessage.warning('请把节点拖入到画布中');

View File

@ -1,143 +0,0 @@
<template>
<div class="system-add-dept-container">
<el-dialog title="新增部门" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级部门">
<el-cascader
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="state.ruleForm.deptLevel"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门名称">
<el-input v-model="state.ruleForm.deptName" placeholder="请输入部门名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="负责人">
<el-input v-model="state.ruleForm.person" placeholder="请输入负责人" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门状态">
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="部门描述">
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入部门描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemAddDept">
import { reactive, onMounted } from 'vue';
// 定义变量内容
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [], // 上级部门
deptName: '', // 部门名称
person: '', // 负责人
phone: '', // 手机号
email: '', // 邮箱
sort: 0, // 排序
status: true, // 部门状态
describe: '', // 部门描述
},
deptData: [] as DeptTreeType[], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '总部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="system-edit-dept-container"> <div class="system-dept-dialog-container">
<el-dialog title="修改部门" v-model="state.isShowDialog" width="769px"> <el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px"> <el-form ref="deptDialogFormRef" :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35"> <el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20"> <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级部门"> <el-form-item label="上级部门">
@ -60,19 +60,22 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button> <el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button> <el-button type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemEditDept"> <script setup lang="ts" name="systemDeptDialog">
import { reactive, onMounted } from 'vue'; import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
// //
const deptDialogFormRef = ref();
const state = reactive({ const state = reactive({
isShowDialog: false,
ruleForm: { ruleForm: {
deptLevel: [] as string[], // deptLevel: [] as string[], //
deptName: '', // deptName: '', //
@ -84,31 +87,51 @@ const state = reactive({
describe: '', // describe: '', //
}, },
deptData: [] as DeptTreeType[], // deptData: [] as DeptTreeType[], //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
}); });
// //
const openDialog = (row: RowDeptType) => { const openDialog = (type: string, row: RowDeptType) => {
row.deptLevel = ['vueNextAdmin']; if (type === 'edit') {
row.person = 'lyt'; row.deptLevel = ['vueNextAdmin'];
row.phone = '12345678910'; row.person = 'lyt';
row.email = 'vueNextAdmin@123.com'; row.phone = '12345678910';
state.ruleForm = row; row.email = 'vueNextAdmin@123.com';
state.isShowDialog = true; state.ruleForm = row;
state.dialog.title = '修改部门';
state.dialog.submitTxt = '修 改';
} else {
state.dialog.title = '新增部门';
state.dialog.submitTxt = '新 增';
// 使
// nextTick(() => {
// deptDialogFormRef.value.resetFields();
// });
}
state.dialog.isShowDialog = true;
getMenuData();
}; };
// //
const closeDialog = () => { const closeDialog = () => {
state.isShowDialog = false; state.dialog.isShowDialog = false;
}; };
// //
const onCancel = () => { const onCancel = () => {
closeDialog(); closeDialog();
}; };
// //
const onSubmit = () => { const onSubmit = () => {
closeDialog(); closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
}; };
// //
const initTableData = () => { const getMenuData = () => {
state.deptData.push({ state.deptData.push({
deptName: 'vueNextAdmin', deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(), createTime: new Date().toLocaleString(),
@ -136,10 +159,6 @@ const initTableData = () => {
], ],
}); });
}; };
//
onMounted(() => {
initTableData();
});
// //
defineExpose({ defineExpose({

View File

@ -9,7 +9,7 @@
</el-icon> </el-icon>
查询 查询
</el-button> </el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddDept"> <el-button size="default" type="success" class="ml10" @click="onOpenAddDept('add')">
<el-icon> <el-icon>
<ele-FolderAdd /> <ele-FolderAdd />
</el-icon> </el-icon>
@ -18,6 +18,7 @@
</div> </div>
<el-table <el-table
:data="state.tableData.data" :data="state.tableData.data"
v-loading="state.tableData.loading"
style="width: 100%" style="width: 100%"
row-key="id" row-key="id"
default-expand-all default-expand-all
@ -39,15 +40,14 @@
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column> <el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140"> <el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope"> <template #default="scope">
<el-button size="small" text type="primary" @click="onOpenAddDept">新增</el-button> <el-button size="small" text type="primary" @click="onOpenAddDept('add')">新增</el-button>
<el-button size="small" text type="primary" @click="onOpenEditDept(scope.row)">修改</el-button> <el-button size="small" text type="primary" @click="onOpenEditDept('edit', scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onTabelRowDel(scope.row)">删除</el-button> <el-button size="small" text type="primary" @click="onTabelRowDel(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-card> </el-card>
<AddDept ref="addDeptRef" /> <DeptDialog ref="deptDialogRef" @refresh="getTableData()" />
<EditDept ref="editDeptRef" />
</div> </div>
</template> </template>
@ -56,12 +56,10 @@ import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件 // 引入组件
const AddDept = defineAsyncComponent(() => import('/@/views/system/dept/component/addDept.vue')); const DeptDialog = defineAsyncComponent(() => import('/@/views/system/dept/dialog.vue'));
const EditDept = defineAsyncComponent(() => import('/@/views/system/dept/component/editDept.vue'));
// 定义变量内容 // 定义变量内容
const addDeptRef = ref(); const deptDialogRef = ref();
const editDeptRef = ref();
const state = reactive<SysDeptState>({ const state = reactive<SysDeptState>({
tableData: { tableData: {
data: [], data: [],
@ -75,7 +73,9 @@ const state = reactive<SysDeptState>({
}); });
// 初始化表格数据 // 初始化表格数据
const initTableData = () => { const getTableData = () => {
state.tableData.loading = true;
state.tableData.data = [];
state.tableData.data.push({ state.tableData.data.push({
deptName: 'vueNextAdmin', deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(), createTime: new Date().toLocaleString(),
@ -103,14 +103,17 @@ const initTableData = () => {
], ],
}); });
state.tableData.total = state.tableData.data.length; state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
}; };
// 打开新增菜单弹窗 // 打开新增菜单弹窗
const onOpenAddDept = () => { const onOpenAddDept = (type: string) => {
addDeptRef.value.openDialog(); deptDialogRef.value.openDialog(type);
}; };
// 打开编辑菜单弹窗 // 打开编辑菜单弹窗
const onOpenEditDept = (row: DeptTreeType) => { const onOpenEditDept = (type: string, row: DeptTreeType) => {
editDeptRef.value.openDialog(row); deptDialogRef.value.openDialog(type, row);
}; };
// 删除当前行 // 删除当前行
const onTabelRowDel = (row: DeptTreeType) => { const onTabelRowDel = (row: DeptTreeType) => {
@ -120,12 +123,13 @@ const onTabelRowDel = (row: DeptTreeType) => {
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
getTableData();
ElMessage.success('删除成功'); ElMessage.success('删除成功');
}) })
.catch(() => {}); .catch(() => {});
}; };
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
initTableData(); getTableData();
}); });
</script> </script>

View File

@ -1,114 +0,0 @@
<template>
<div class="system-add-dic-container">
<el-dialog title="新增字典" v-model="state.isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字典名称">
<el-input v-model="state.ruleForm.dicName" placeholder="请输入字典名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字段名">
<el-input v-model="state.ruleForm.fieldName" placeholder="请输入字段名,拼接 ruleForm.list" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典状态">
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-row :gutter="35" v-for="(v, k) in state.ruleForm.list" :key="k">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :prop="`list[${k}].label`">
<template #label>
<el-button type="primary" circle size="small" @click="onAddRow" v-if="k === 0">
<el-icon>
<ele-Plus />
</el-icon>
</el-button>
<el-button type="danger" circle size="small" @click="onDelRow(k)" v-else>
<el-icon>
<ele-Delete />
</el-icon>
</el-button>
<span class="ml10">字段</span>
</template>
<el-input v-model="v.label" style="width: 100%" placeholder="请输入字段名"> </el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="属性" :prop="`list[${k}].value`">
<el-input v-model="v.value" style="width: 100%" placeholder="请输入属性值"> </el-input>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典描述">
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入字典描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemAddDic">
import { reactive } from 'vue';
// 定义变量内容
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', // 字典名称
fieldName: '', // 字段名
status: true, // 字典状态
list: [] as ListType[], // 子集字段 + 属性值
describe: '', // 字典描述
},
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 新增行
const onAddRow = () => {
state.ruleForm.list.push({
id: Math.random(),
label: '',
value: '',
});
};
// 删除行
const onDelRow = (k: number) => {
state.ruleForm.list.splice(k, 1);
};
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="system-edit-dic-container"> <div class="system-dic-dialog-container">
<el-dialog title="修改字典" v-model="state.isShowDialog" width="769px"> <el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert> <el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="state.ruleForm" size="default" label-width="90px"> <el-form ref="dicDialogFormRef" :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35"> <el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字典名称"> <el-form-item label="字典名称">
@ -56,19 +56,22 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button> <el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button> <el-button type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemEditDic"> <script setup lang="ts" name="systemDicDialog">
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
// //
const dicDialogFormRef = ref();
const state = reactive({ const state = reactive({
isShowDialog: false,
ruleForm: { ruleForm: {
dicName: '', // dicName: '', //
fieldName: '', // fieldName: '', //
@ -76,37 +79,56 @@ const state = reactive({
list: [] as ListType[], // + list: [] as ListType[], // +
describe: '', // describe: '', //
}, },
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
}); });
// //
const openDialog = (row: RowDicType) => { const openDialog = (type: string, row: RowDicType) => {
if (row.fieldName === 'SYS_UERINFO') { if (type === 'edit') {
row.list = [ if (row.fieldName === 'SYS_UERINFO') {
{ id: Math.random(), label: 'sex', value: '1' }, row.list = [
{ id: Math.random(), label: 'sex', value: '0' }, { id: Math.random(), label: 'sex', value: '1' },
]; { id: Math.random(), label: 'sex', value: '0' },
];
} else {
row.list = [
{ id: Math.random(), label: 'role', value: 'admin' },
{ id: Math.random(), label: 'role', value: 'common' },
{ id: Math.random(), label: 'roleName', value: '超级管理员' },
{ id: Math.random(), label: 'roleName', value: '普通用户' },
];
}
state.ruleForm = row;
state.dialog.title = '修改字典';
state.dialog.submitTxt = '修 改';
} else { } else {
row.list = [ state.dialog.title = '新增字典';
{ id: Math.random(), label: 'role', value: 'admin' }, state.dialog.submitTxt = '新 增';
{ id: Math.random(), label: 'role', value: 'common' }, // 使
{ id: Math.random(), label: 'roleName', value: '超级管理员' }, // nextTick(() => {
{ id: Math.random(), label: 'roleName', value: '普通用户' }, // dicDialogFormRef.value.resetFields();
]; // });
} }
state.ruleForm = row; state.dialog.isShowDialog = true;
state.isShowDialog = true;
}; };
// //
const closeDialog = () => { const closeDialog = () => {
state.isShowDialog = false; state.dialog.isShowDialog = false;
}; };
// //
const onCancel = () => { const onCancel = () => {
closeDialog(); closeDialog();
}; };
// //
const onSubmit = () => { const onSubmit = () => {
closeDialog(); closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
}; };
// //
const onAddRow = () => { const onAddRow = () => {

View File

@ -9,14 +9,14 @@
</el-icon> </el-icon>
查询 查询
</el-button> </el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddDic"> <el-button size="default" type="success" class="ml10" @click="onOpenAddDic('add')">
<el-icon> <el-icon>
<ele-FolderAdd /> <ele-FolderAdd />
</el-icon> </el-icon>
新增字典 新增字典
</el-button> </el-button>
</div> </div>
<el-table :data="state.tableData.data" style="width: 100%"> <el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
<el-table-column type="index" label="序号" width="50" /> <el-table-column type="index" label="序号" width="50" />
<el-table-column prop="dicName" label="字典名称" show-overflow-tooltip></el-table-column> <el-table-column prop="dicName" label="字典名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="fieldName" label="字段名" show-overflow-tooltip></el-table-column> <el-table-column prop="fieldName" label="字段名" show-overflow-tooltip></el-table-column>
@ -30,7 +30,7 @@
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column> <el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100"> <el-table-column label="操作" width="100">
<template #default="scope"> <template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEditDic(scope.row)">修改</el-button> <el-button size="small" text type="primary" @click="onOpenEditDic('edit', scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button> <el-button size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -49,8 +49,7 @@
> >
</el-pagination> </el-pagination>
</el-card> </el-card>
<AddDic ref="addDicRef" /> <DicDialog ref="dicDialogRef" @refresh="getTableData()" />
<EditDic ref="editDicRef" />
</div> </div>
</template> </template>
@ -59,12 +58,10 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件 // 引入组件
const AddDic = defineAsyncComponent(() => import('/@/views/system/dic/component/addDic.vue')); const DicDialog = defineAsyncComponent(() => import('/@/views/system/dic/dialog.vue'));
const EditDic = defineAsyncComponent(() => import('/@/views/system/dic/component/editDic.vue'));
// 定义变量内容 // 定义变量内容
const addDicRef = ref(); const dicDialogRef = ref();
const editDicRef = ref();
const state = reactive<SysDicState>({ const state = reactive<SysDicState>({
tableData: { tableData: {
data: [], data: [],
@ -78,7 +75,8 @@ const state = reactive<SysDicState>({
}); });
// 初始化表格数据 // 初始化表格数据
const initTableData = () => { const getTableData = () => {
state.tableData.loading = true;
const data = []; const data = [];
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
data.push({ data.push({
@ -92,14 +90,17 @@ const initTableData = () => {
} }
state.tableData.data = data; state.tableData.data = data;
state.tableData.total = state.tableData.data.length; state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
}; };
// 打开新增字典弹窗 // 打开新增字典弹窗
const onOpenAddDic = () => { const onOpenAddDic = (type: string) => {
addDicRef.value.openDialog(); dicDialogRef.value.openDialog(type);
}; };
// 打开修改字典弹窗 // 打开修改字典弹窗
const onOpenEditDic = (row: RowDicType) => { const onOpenEditDic = (type: string, row: RowDicType) => {
editDicRef.value.openDialog(row); dicDialogRef.value.openDialog(type, row);
}; };
// 删除字典 // 删除字典
const onRowDel = (row: RowDicType) => { const onRowDel = (row: RowDicType) => {
@ -109,6 +110,7 @@ const onRowDel = (row: RowDicType) => {
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
getTableData();
ElMessage.success('删除成功'); ElMessage.success('删除成功');
}) })
.catch(() => {}); .catch(() => {});
@ -116,13 +118,15 @@ const onRowDel = (row: RowDicType) => {
// 分页改变 // 分页改变
const onHandleSizeChange = (val: number) => { const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val; state.tableData.param.pageSize = val;
getTableData();
}; };
// 分页改变 // 分页改变
const onHandleCurrentChange = (val: number) => { const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val; state.tableData.param.pageNum = val;
getTableData();
}; };
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
initTableData(); getTableData();
}); });
</script> </script>

View File

@ -1,237 +0,0 @@
<template>
<div class="system-edit-menu-container">
<el-dialog title="修改菜单" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="80px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级菜单">
<el-cascader
:options="state.menuData"
:props="{ checkStrictly: true, value: 'path', label: 'title' }"
placeholder="请选择上级菜单"
clearable
class="w100"
v-model="state.ruleForm.menuSuperior"
>
<template #default="{ node, data }">
<span>{{ data.title }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单类型">
<el-radio-group v-model="state.ruleForm.menuType">
<el-radio label="menu">菜单</el-radio>
<el-radio label="btn">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单名称">
<el-input v-model="state.ruleForm.meta.title" placeholder="格式message.router.xxx" clearable></el-input>
</el-form-item>
</el-col>
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由名称">
<el-input v-model="state.ruleForm.name" placeholder="路由中的 name 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由路径">
<el-input v-model="state.ruleForm.path" placeholder="路由中的 path 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="重定向">
<el-input v-model="state.ruleForm.redirect" placeholder="请输入路由重定向" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" type="all" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="组件路径">
<el-input v-model="state.ruleForm.component" placeholder="组件路径" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="链接地址">
<el-input
v-model="state.ruleForm.meta.isLink"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="!state.ruleForm.isLink"
>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-select v-model="state.ruleForm.meta.roles" multiple placeholder="取角色管理" clearable class="w100">
<el-option label="admin" value="admin"></el-option>
<el-option label="common" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
</template>
<template v-if="state.ruleForm.menuType === 'btn'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-input v-model="state.ruleForm.btnPower" placeholder="请输入权限标识" clearable></el-input>
</el-form-item>
</el-col>
</template>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单排序">
<el-input-number v-model="state.ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否隐藏">
<el-radio-group v-model="state.ruleForm.meta.isHide">
<el-radio :label="true">隐藏</el-radio>
<el-radio :label="false">不隐藏</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="页面缓存">
<el-radio-group v-model="state.ruleForm.meta.isKeepAlive">
<el-radio :label="true">缓存</el-radio>
<el-radio :label="false">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否固定">
<el-radio-group v-model="state.ruleForm.meta.isAffix">
<el-radio :label="true">固定</el-radio>
<el-radio :label="false">不固定</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否外链">
<el-radio-group v-model="state.ruleForm.isLink" :disabled="state.ruleForm.meta.isIframe">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否内嵌">
<el-radio-group v-model="state.ruleForm.meta.isIframe" @change="onSelectIframeChange">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemEditMenu">
import { defineAsyncComponent, reactive, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { i18n } from '/@/i18n/index';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
// 引入组件
const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelector/index.vue'));
// 定义变量内容
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);
const state = reactive({
isShowDialog: false,
// 参数请参考 `/src/router/route.ts` 中的 `dynamicRoutes` 路由菜单格式
ruleForm: {
menuSuperior: [], // 上级菜单
menuType: 'menu', // 菜单类型
name: '', // 路由名称
component: '', // 组件路径
isLink: false, // 是否外链
menuSort: 0, // 菜单排序
path: '', // 路由路径
redirect: '', // 路由重定向,有子集 children 时
meta: {
title: '', // 菜单名称
icon: '', // 菜单图标
isHide: false, // 是否隐藏
isKeepAlive: true, // 是否缓存
isAffix: false, // 是否固定
isLink: '', // 外链/内嵌时链接地址http:xxx.com开启外链条件`1、isLink: 链接地址不为空`
isIframe: false, // 是否内嵌,开启条件,`1、isIframe:true 2、isLink链接地址不为空`
roles: '', // 权限标识,取角色管理
},
btnPower: '', // 菜单类型为按钮时,权限标识
},
menuData: [] as RouteItems, // 上级菜单数据
});
// 获取 pinia 中的路由
const getMenuData = (routes: RouteItems) => {
const arr: RouteItems = [];
routes.map((val: RouteItem) => {
val['title'] = i18n.global.t(val.meta?.title as string);
arr.push({ ...val });
if (val.children) getMenuData(val.children);
});
return arr;
};
// 打开弹窗
const openDialog = (row: any) => {
// 模拟数据,实际请走接口
row.menuType = 'menu';
row.menuSort = Math.random();
row.component = `${row.component} `
.match(/\'(.+)\'/g)
?.join('')
.replace(/\'/g, '');
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 是否内嵌下拉改变
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe) state.ruleForm.isLink = true;
else state.ruleForm.isLink = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog(); // 关闭弹窗
// setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
};
// 页面加载时
onMounted(() => {
state.menuData = getMenuData(routesList.value);
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="system-add-menu-container"> <div class="system-menu-dialog-container">
<el-dialog title="新增菜单" v-model="state.isShowDialog" width="769px"> <el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="80px"> <el-form ref="menuDialogFormRef" :model="state.ruleForm" size="default" label-width="80px">
<el-row :gutter="35"> <el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20"> <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级菜单"> <el-form-item label="上级菜单">
@ -51,12 +51,12 @@
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单图标"> <el-form-item label="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" type="all" /> <IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="组件路径"> <el-form-item label="组件路径">
<el-input v-model="state.ruleForm.component" placeholder="组件路径" clearable></el-input> <el-input v-model="state.ruleForm.componentAlias" placeholder="组件路径" clearable></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@ -138,34 +138,38 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button> <el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button> <el-button type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemAddMenu"> <script setup lang="ts" name="systemMenuDialog">
import { defineAsyncComponent, reactive, onMounted } from 'vue'; import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
import { i18n } from '/@/i18n/index'; import { i18n } from '/@/i18n/index';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd"; // import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
// /
const emit = defineEmits(['refresh']);
// //
const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelector/index.vue')); const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelector/index.vue'));
// //
const menuDialogFormRef = ref();
const stores = useRoutesList(); const stores = useRoutesList();
const { routesList } = storeToRefs(stores); const { routesList } = storeToRefs(stores);
const state = reactive({ const state = reactive({
isShowDialog: false,
// `/src/router/route.ts` `dynamicRoutes` // `/src/router/route.ts` `dynamicRoutes`
ruleForm: { ruleForm: {
menuSuperior: [], // menuSuperior: [], //
menuType: 'menu', // menuType: 'menu', //
name: '', // name: '', //
component: '', // component: '', //
componentAlias: '', //
isLink: false, // isLink: false, //
menuSort: 0, // menuSort: 0, //
path: '', // path: '', //
@ -183,6 +187,12 @@ const state = reactive({
btnPower: '', // btnPower: '', //
}, },
menuData: [] as RouteItems, // menuData: [] as RouteItems, //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
}); });
// pinia // pinia
@ -196,12 +206,28 @@ const getMenuData = (routes: RouteItems) => {
return arr; return arr;
}; };
// //
const openDialog = () => { const openDialog = (type: string, row?: any) => {
state.isShowDialog = true; if (type === 'edit') {
//
row.menuType = 'menu';
row.menuSort = Math.floor(Math.random() * 100);
state.ruleForm = JSON.parse(JSON.stringify(row));
state.dialog.title = '修改菜单';
state.dialog.submitTxt = '修 改';
} else {
state.dialog.title = '新增菜单';
state.dialog.submitTxt = '新 增';
// 使
// nextTick(() => {
// menuDialogFormRef.value.resetFields();
// });
}
state.dialog.type = type;
state.dialog.isShowDialog = true;
}; };
// //
const closeDialog = () => { const closeDialog = () => {
state.isShowDialog = false; state.dialog.isShowDialog = false;
}; };
// //
const onSelectIframeChange = () => { const onSelectIframeChange = () => {
@ -212,9 +238,11 @@ const onSelectIframeChange = () => {
const onCancel = () => { const onCancel = () => {
closeDialog(); closeDialog();
}; };
// //
const onSubmit = () => { const onSubmit = () => {
closeDialog(); // closeDialog(); //
emit('refresh');
// if (state.dialog.type === 'add') { }
// setBackEndControlRefreshRoutes() // // setBackEndControlRefreshRoutes() //
}; };
// //

View File

@ -16,7 +16,13 @@
新增菜单 新增菜单
</el-button> </el-button>
</div> </div>
<el-table :data="menuTableData" style="width: 100%" row-key="path" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> <el-table
:data="state.tableData.data"
v-loading="state.tableData.loading"
style="width: 100%"
row-key="path"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="菜单名称" show-overflow-tooltip> <el-table-column label="菜单名称" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
<SvgIcon :name="scope.row.meta.icon" /> <SvgIcon :name="scope.row.meta.icon" />
@ -46,46 +52,54 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140"> <el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope"> <template #default="scope">
<el-button size="small" text type="primary" @click="onOpenAddMenu">新增</el-button> <el-button size="small" text type="primary" @click="onOpenAddMenu('add')">新增</el-button>
<el-button size="small" text type="primary" @click="onOpenEditMenu(scope.row)">修改</el-button> <el-button size="small" text type="primary" @click="onOpenEditMenu('edit', scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onTabelRowDel(scope.row)">删除</el-button> <el-button size="small" text type="primary" @click="onTabelRowDel(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-card> </el-card>
<AddMenu ref="addMenuRef" /> <MenuDialog ref="menuDialogRef" @refresh="getTableData()" />
<EditMenu ref="editMenuRef" />
</div> </div>
</template> </template>
<script setup lang="ts" name="systemMenu"> <script setup lang="ts" name="systemMenu">
import { defineAsyncComponent, ref, computed } from 'vue'; import { defineAsyncComponent, ref, onMounted, reactive } from 'vue';
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
// 引入组件 // 引入组件
const AddMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/addMenu.vue')); const MenuDialog = defineAsyncComponent(() => import('/@/views/system/menu/dialog.vue'));
const EditMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/editMenu.vue'));
// 定义变量内容 // 定义变量内容
const stores = useRoutesList(); const stores = useRoutesList();
const { routesList } = storeToRefs(stores); const { routesList } = storeToRefs(stores);
const addMenuRef = ref(); const menuDialogRef = ref();
const editMenuRef = ref(); const state = reactive({
tableData: {
// 获取 pinia 中的路由 data: [] as RouteRecordRaw[],
const menuTableData = computed(() => { loading: true,
return routesList.value; },
}); });
// 获取路由数据,真实请从接口获取
const getTableData = () => {
state.tableData.loading = true;
state.tableData.data = routesList.value;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
};
// 打开新增菜单弹窗 // 打开新增菜单弹窗
const onOpenAddMenu = () => { const onOpenAddMenu = (type: string) => {
addMenuRef.value.openDialog(); menuDialogRef.value.openDialog(type);
}; };
// 打开编辑菜单弹窗 // 打开编辑菜单弹窗
const onOpenEditMenu = (row: RouteRecordRaw) => { const onOpenEditMenu = (type: string, row: RouteRecordRaw) => {
editMenuRef.value.openDialog(row); menuDialogRef.value.openDialog(type, row);
}; };
// 删除当前行 // 删除当前行
const onTabelRowDel = (row: RouteRecordRaw) => { const onTabelRowDel = (row: RouteRecordRaw) => {
@ -96,7 +110,13 @@ const onTabelRowDel = (row: RouteRecordRaw) => {
}) })
.then(() => { .then(() => {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getTableData();
//await setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
}) })
.catch(() => {}); .catch(() => {});
}; };
// 页面加载时
onMounted(() => {
getTableData();
});
</script> </script>

View File

@ -1,221 +0,0 @@
<template>
<div class="system-edit-role-container">
<el-dialog title="修改角色" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色名称">
<el-input v-model="state.ruleForm.roleName" placeholder="请输入角色名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色标识">
<template #label>
<el-tooltip effect="dark" content="用于 `router/route.ts` meta.roles" placement="top-start">
<span>角色标识</span>
</el-tooltip>
</template>
<el-input v-model="state.ruleForm.roleSign" placeholder="请输入角色标识" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色状态">
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="角色描述">
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入角色描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单权限">
<el-tree
:data="state.menuData"
:props="state.menuProps"
:default-checked-keys="[112, 113]"
node-key="id"
show-checkbox
class="menu-data-tree"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemEditRole">
import { reactive } from 'vue';
// 定义变量内容
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', // 角色名称
roleSign: '', // 角色标识
sort: 0, // 排序
status: true, // 角色状态
describe: '', // 角色描述
},
menuData: [] as TreeType[],
menuProps: {
children: 'children',
label: 'label',
},
});
// 打开弹窗
const openDialog = (row: RowRoleType) => {
state.ruleForm = row;
state.isShowDialog = true;
getMenuData();
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 获取菜单结构数据
const getMenuData = () => {
state.menuData = [
{
id: 1,
label: '系统管理',
children: [
{
id: 11,
label: '菜单管理',
children: [
{
id: 111,
label: '菜单新增',
},
{
id: 112,
label: '菜单修改',
},
{
id: 113,
label: '菜单删除',
},
{
id: 114,
label: '菜单查询',
},
],
},
{
id: 12,
label: '角色管理',
children: [
{
id: 121,
label: '角色新增',
},
{
id: 122,
label: '角色修改',
},
{
id: 123,
label: '角色删除',
},
{
id: 124,
label: '角色查询',
},
],
},
{
id: 13,
label: '用户管理',
children: [
{
id: 131,
label: '用户新增',
},
{
id: 132,
label: '用户修改',
},
{
id: 133,
label: '用户删除',
},
{
id: 134,
label: '用户查询',
},
],
},
],
},
{
id: 2,
label: '权限管理',
children: [
{
id: 21,
label: '前端控制',
children: [
{
id: 211,
label: '页面权限',
},
{
id: 212,
label: '页面权限',
},
],
},
{
id: 22,
label: '后端控制',
children: [
{
id: 221,
label: '页面权限',
},
],
},
],
},
];
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
.system-edit-role-container {
.menu-data-tree {
width: 100%;
border: 1px solid var(--el-border-color);
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
padding: 5px;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="system-add-role-container"> <div class="system-role-dialog-container">
<el-dialog title="新增角色" v-model="state.isShowDialog" width="769px"> <el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px"> <el-form ref="roleDialogFormRef" :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35"> <el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色名称"> <el-form-item label="角色名称">
@ -43,19 +43,22 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button> <el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button> <el-button type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemAddRole"> <script setup lang="ts" name="systemRoleDialog">
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
// //
const roleDialogFormRef = ref();
const state = reactive({ const state = reactive({
isShowDialog: false,
ruleForm: { ruleForm: {
roleName: '', // roleName: '', //
roleSign: '', // roleSign: '', //
@ -68,24 +71,44 @@ const state = reactive({
children: 'children', children: 'children',
label: 'label', label: 'label',
}, },
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
}); });
// //
const openDialog = () => { const openDialog = (type: string, row: RowRoleType) => {
state.isShowDialog = true; if (type === 'edit') {
state.ruleForm = row;
state.dialog.title = '修改角色';
state.dialog.submitTxt = '修 改';
} else {
state.dialog.title = '新增角色';
state.dialog.submitTxt = '新 增';
// 使
// nextTick(() => {
// roleDialogFormRef.value.resetFields();
// });
}
state.dialog.isShowDialog = true;
getMenuData(); getMenuData();
}; };
// //
const closeDialog = () => { const closeDialog = () => {
state.isShowDialog = false; state.dialog.isShowDialog = false;
}; };
// //
const onCancel = () => { const onCancel = () => {
closeDialog(); closeDialog();
}; };
// //
const onSubmit = () => { const onSubmit = () => {
closeDialog(); closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
}; };
// //
const getMenuData = () => { const getMenuData = () => {
@ -202,7 +225,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.system-add-role-container { .system-role-dialog-container {
.menu-data-tree { .menu-data-tree {
width: 100%; width: 100%;
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);

View File

@ -9,14 +9,14 @@
</el-icon> </el-icon>
查询 查询
</el-button> </el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddRole"> <el-button size="default" type="success" class="ml10" @click="onOpenAddRole('add')">
<el-icon> <el-icon>
<ele-FolderAdd /> <ele-FolderAdd />
</el-icon> </el-icon>
新增角色 新增角色
</el-button> </el-button>
</div> </div>
<el-table :data="state.tableData.data" style="width: 100%"> <el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
<el-table-column type="index" label="序号" width="60" /> <el-table-column type="index" label="序号" width="60" />
<el-table-column prop="roleName" label="角色名称" show-overflow-tooltip></el-table-column> <el-table-column prop="roleName" label="角色名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="roleSign" label="角色标识" show-overflow-tooltip></el-table-column> <el-table-column prop="roleSign" label="角色标识" show-overflow-tooltip></el-table-column>
@ -31,7 +31,7 @@
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column> <el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100"> <el-table-column label="操作" width="100">
<template #default="scope"> <template #default="scope">
<el-button :disabled="scope.row.roleName === '超级管理员'" size="small" text type="primary" @click="onOpenEditRole(scope.row)" <el-button :disabled="scope.row.roleName === '超级管理员'" size="small" text type="primary" @click="onOpenEditRole('edit', scope.row)"
>修改</el-button >修改</el-button
> >
<el-button :disabled="scope.row.roleName === '超级管理员'" size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button> <el-button :disabled="scope.row.roleName === '超级管理员'" size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
@ -52,8 +52,7 @@
> >
</el-pagination> </el-pagination>
</div> </div>
<AddRole ref="addRoleRef" /> <RoleDialog ref="roleDialogRef" @refresh="getTableData()" />
<EditRole ref="editRoleRef" />
</div> </div>
</template> </template>
@ -62,12 +61,10 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件 // 引入组件
const AddRole = defineAsyncComponent(() => import('/@/views/system/role/component/addRole.vue')); const RoleDialog = defineAsyncComponent(() => import('/@/views/system/role/dialog.vue'));
const EditRole = defineAsyncComponent(() => import('/@/views/system/role/component/editRole.vue'));
// 定义变量内容 // 定义变量内容
const addRoleRef = ref(); const roleDialogRef = ref();
const editRoleRef = ref();
const state = reactive<SysRoleState>({ const state = reactive<SysRoleState>({
tableData: { tableData: {
data: [], data: [],
@ -81,7 +78,8 @@ const state = reactive<SysRoleState>({
}, },
}); });
// 初始化表格数据 // 初始化表格数据
const initTableData = () => { const getTableData = () => {
state.tableData.loading = true;
const data = []; const data = [];
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
data.push({ data.push({
@ -95,14 +93,17 @@ const initTableData = () => {
} }
state.tableData.data = data; state.tableData.data = data;
state.tableData.total = state.tableData.data.length; state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
}; };
// 打开新增角色弹窗 // 打开新增角色弹窗
const onOpenAddRole = () => { const onOpenAddRole = (type: string) => {
addRoleRef.value.openDialog(); roleDialogRef.value.openDialog(type);
}; };
// 打开修改角色弹窗 // 打开修改角色弹窗
const onOpenEditRole = (row: Object) => { const onOpenEditRole = (type: string, row: Object) => {
editRoleRef.value.openDialog(row); roleDialogRef.value.openDialog(type, row);
}; };
// 删除角色 // 删除角色
const onRowDel = (row: RowRoleType) => { const onRowDel = (row: RowRoleType) => {
@ -112,6 +113,7 @@ const onRowDel = (row: RowRoleType) => {
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
getTableData();
ElMessage.success('删除成功'); ElMessage.success('删除成功');
}) })
.catch(() => {}); .catch(() => {});
@ -119,14 +121,16 @@ const onRowDel = (row: RowRoleType) => {
// 分页改变 // 分页改变
const onHandleSizeChange = (val: number) => { const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val; state.tableData.param.pageSize = val;
getTableData();
}; };
// 分页改变 // 分页改变
const onHandleCurrentChange = (val: number) => { const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val; state.tableData.param.pageNum = val;
getTableData();
}; };
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
initTableData(); getTableData();
}); });
</script> </script>

View File

@ -1,167 +0,0 @@
<template>
<div class="system-add-user-container">
<el-dialog title="新增用户" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户名称">
<el-input v-model="state.ruleForm.userName" placeholder="请输入账户名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户昵称">
<el-input v-model="state.ruleForm.userNickname" placeholder="请输入用户昵称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="关联角色">
<el-select v-model="state.ruleForm.roleSign" placeholder="请选择" clearable class="w100">
<el-option label="超级管理员" value="admin"></el-option>
<el-option label="普通用户" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门">
<el-cascader
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="state.ruleForm.department"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="性别">
<el-select v-model="state.ruleForm.sex" placeholder="请选择" clearable class="w100">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户密码">
<el-input v-model="state.ruleForm.password" placeholder="请输入" type="password" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户过期">
<el-date-picker v-model="state.ruleForm.overdueTime" type="date" placeholder="请选择" class="w100"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户状态">
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="用户描述">
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入用户描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemAddUser">
import { reactive, onMounted } from 'vue';
// 定义变量内容
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', // 账户名称
userNickname: '', // 用户昵称
roleSign: '', // 关联角色
department: [], // 部门
phone: '', // 手机号
email: '', // 邮箱
sex: '', // 性别
password: '', // 账户密码
overdueTime: '', // 账户过期
status: true, // 用户状态
describe: '', // 用户描述
},
deptData: [] as DeptTreeType[], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '总部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Math.random(),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="system-edit-user-container"> <div class="system-user-dialog-container">
<el-dialog title="修改用户" v-model="state.isShowDialog" width="769px"> <el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px"> <el-form ref="userDialogFormRef" :model="state.ruleForm" size="default" label-width="90px">
<el-row :gutter="35"> <el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户名称"> <el-form-item label="账户名称">
@ -81,19 +81,22 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button> <el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button> <el-button type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="systemEditUser"> <script setup lang="ts" name="systemUserDialog">
import { reactive, onMounted } from 'vue'; import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
// //
const userDialogFormRef = ref();
const state = reactive({ const state = reactive({
isShowDialog: false,
ruleForm: { ruleForm: {
userName: '', // userName: '', //
userNickname: '', // userNickname: '', //
@ -108,27 +111,47 @@ const state = reactive({
describe: '', // describe: '', //
}, },
deptData: [] as DeptTreeType[], // deptData: [] as DeptTreeType[], //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
}); });
// //
const openDialog = (row: RowUserType) => { const openDialog = (type: string, row: RowUserType) => {
state.ruleForm = row; if (type === 'edit') {
state.isShowDialog = true; state.ruleForm = row;
state.dialog.title = '修改用户';
state.dialog.submitTxt = '修 改';
} else {
state.dialog.title = '新增用户';
state.dialog.submitTxt = '新 增';
// 使
// nextTick(() => {
// userDialogFormRef.value.resetFields();
// });
}
state.dialog.isShowDialog = true;
getMenuData();
}; };
// //
const closeDialog = () => { const closeDialog = () => {
state.isShowDialog = false; state.dialog.isShowDialog = false;
}; };
// //
const onCancel = () => { const onCancel = () => {
closeDialog(); closeDialog();
}; };
// //
const onSubmit = () => { const onSubmit = () => {
closeDialog(); closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
}; };
// //
const initTableData = () => { const getMenuData = () => {
state.deptData.push({ state.deptData.push({
deptName: 'vueNextAdmin', deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(), createTime: new Date().toLocaleString(),
@ -156,10 +179,6 @@ const initTableData = () => {
], ],
}); });
}; };
//
onMounted(() => {
initTableData();
});
// //
defineExpose({ defineExpose({

View File

@ -9,14 +9,14 @@
</el-icon> </el-icon>
查询 查询
</el-button> </el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddUser"> <el-button size="default" type="success" class="ml10" @click="onOpenAddUser('add')">
<el-icon> <el-icon>
<ele-FolderAdd /> <ele-FolderAdd />
</el-icon> </el-icon>
新增用户 新增用户
</el-button> </el-button>
</div> </div>
<el-table :data="state.tableData.data" style="width: 100%"> <el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
<el-table-column type="index" label="序号" width="60" /> <el-table-column type="index" label="序号" width="60" />
<el-table-column prop="userName" label="账户名称" show-overflow-tooltip></el-table-column> <el-table-column prop="userName" label="账户名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="userNickname" label="用户昵称" show-overflow-tooltip></el-table-column> <el-table-column prop="userNickname" label="用户昵称" show-overflow-tooltip></el-table-column>
@ -34,7 +34,9 @@
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column> <el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100"> <el-table-column label="操作" width="100">
<template #default="scope"> <template #default="scope">
<el-button :disabled="scope.row.userName === 'admin'" size="small" text type="primary" @click="onOpenEditUser(scope.row)">修改</el-button> <el-button :disabled="scope.row.userName === 'admin'" size="small" text type="primary" @click="onOpenEditUser('edit', scope.row)"
>修改</el-button
>
<el-button :disabled="scope.row.userName === 'admin'" size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button> <el-button :disabled="scope.row.userName === 'admin'" size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -53,8 +55,7 @@
> >
</el-pagination> </el-pagination>
</el-card> </el-card>
<AddUer ref="addUserRef" /> <UserDialog ref="userDialogRef" @refresh="getTableData()" />
<EditUser ref="editUserRef" />
</div> </div>
</template> </template>
@ -63,12 +64,10 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件 // 引入组件
const AddUer = defineAsyncComponent(() => import('/@/views/system/user/component/addUser.vue')); const UserDialog = defineAsyncComponent(() => import('/@/views/system/user/dialog.vue'));
const EditUser = defineAsyncComponent(() => import('/@/views/system/user/component/editUser.vue'));
// 定义变量内容 // 定义变量内容
const addUserRef = ref(); const userDialogRef = ref();
const editUserRef = ref();
const state = reactive<SysUserState>({ const state = reactive<SysUserState>({
tableData: { tableData: {
data: [], data: [],
@ -82,7 +81,8 @@ const state = reactive<SysUserState>({
}); });
// 初始化表格数据 // 初始化表格数据
const initTableData = () => { const getTableData = () => {
state.tableData.loading = true;
const data = []; const data = [];
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
data.push({ data.push({
@ -102,14 +102,17 @@ const initTableData = () => {
} }
state.tableData.data = data; state.tableData.data = data;
state.tableData.total = state.tableData.data.length; state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
}; };
// 打开新增用户弹窗 // 打开新增用户弹窗
const onOpenAddUser = () => { const onOpenAddUser = (type: string) => {
addUserRef.value.openDialog(); userDialogRef.value.openDialog(type);
}; };
// 打开修改用户弹窗 // 打开修改用户弹窗
const onOpenEditUser = (row: RowUserType) => { const onOpenEditUser = (type: string, row: RowUserType) => {
editUserRef.value.openDialog(row); userDialogRef.value.openDialog(type, row);
}; };
// 删除用户 // 删除用户
const onRowDel = (row: RowUserType) => { const onRowDel = (row: RowUserType) => {
@ -119,6 +122,7 @@ const onRowDel = (row: RowUserType) => {
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
getTableData();
ElMessage.success('删除成功'); ElMessage.success('删除成功');
}) })
.catch(() => {}); .catch(() => {});
@ -126,14 +130,16 @@ const onRowDel = (row: RowUserType) => {
// 分页改变 // 分页改变
const onHandleSizeChange = (val: number) => { const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val; state.tableData.param.pageSize = val;
getTableData();
}; };
// 分页改变 // 分页改变
const onHandleCurrentChange = (val: number) => { const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val; state.tableData.param.pageNum = val;
getTableData();
}; };
// 页面加载时 // 页面加载时
onMounted(() => { onMounted(() => {
initTableData(); getTableData();
}); });
</script> </script>

View File

@ -126,7 +126,7 @@ const initTime = () => {
const convertData = (data: any) => { const convertData = (data: any) => {
let res = []; let res = [];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
let geoCoord = state.echartsMapData[data[i].name]; let geoCoord = (<any>state.echartsMapData)[data[i].name];
if (geoCoord) { if (geoCoord) {
res.push({ res.push({
name: data[i].name, name: data[i].name,
@ -250,16 +250,16 @@ const initEchartsMap = () => {
// BMAP_SATELLITE_MAP卫星地图 (没有坐标, 绿绿的一片的卫星地图) // BMAP_SATELLITE_MAP卫星地图 (没有坐标, 绿绿的一片的卫星地图)
// BMAP_HYBRID_MAP混合地图 (既有坐标,也是绿绿的一片的卫星地图) // BMAP_HYBRID_MAP混合地图 (既有坐标,也是绿绿的一片的卫星地图)
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
map.setMapType(BMAP_SATELLITE_MAP); map.setMapType(window.BMAP_SATELLITE_MAP);
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
let bdary = new BMap.Boundary(); let bdary = new window.BMap.Boundary();
// 获取行政区域 // 获取行政区域
bdary.get('深圳', function (rs: any) { bdary.get('深圳', function (rs: any) {
// 行政区域的点有多少个 // 行政区域的点有多少个
let count = rs.boundaries.length; let count = rs.boundaries.length;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
let ply = new BMap.Polygon(rs.boundaries[i], { let ply = new window.BMap.Polygon(rs.boundaries[i], {
// 设置多边形边线线粗 // 设置多边形边线线粗
strokeWeight: 4, strokeWeight: 4,
// 设置多边形边线透明度0-1 // 设置多边形边线透明度0-1
@ -280,7 +280,7 @@ const initEchartsMap = () => {
// 初始化地图,设置中心点坐标和地图级别 // 初始化地图,设置中心点坐标和地图级别
// new BMap.Point('深圳市', 11) // new BMap.Point('深圳市', 11)
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
map.centerAndZoom(new BMap.Point(114.064524, 22.549225), 11); map.centerAndZoom(new window.BMap.Point(114.064524, 22.549225), 11);
}); });
}; };
// 产业概况 // 产业概况

View File

@ -127,7 +127,7 @@
<div class="big-data-down-center"> <div class="big-data-down-center">
<div class="big-data-down-center-one"> <div class="big-data-down-center-one">
<div class="big-data-down-center-one-content" ref="rightChartData5"> <div class="big-data-down-center-one-content" ref="rightChartData5">
<div ref="the3DEarth"></div> <div id="3DEarth"></div>
<div :class="v.topLevelClass" v-for="(v, k) in state.earth3DBtnList" :key="k"> <div :class="v.topLevelClass" v-for="(v, k) in state.earth3DBtnList" :key="k">
<div class="circle" v-for="i in 4" :key="i"></div> <div class="circle" v-for="i in 4" :key="i"></div>
<div class="text-box"> <div class="text-box">
@ -235,8 +235,6 @@ import 'echarts-gl';
import { formatDate } from '/@/utils/formatTime'; import { formatDate } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading'; import { NextLoading } from '/@/utils/loading';
import { dropdownList, skyList, dBtnList, earth3DBtnList, chartData4List } from './mock/demo2'; import { dropdownList, skyList, dBtnList, earth3DBtnList, chartData4List } from './mock/demo2';
import worldImg from './images/world.jpg';
import bathymetryImg from './images/bathymetry.jpg';
// 定义变量内容 // 定义变量内容
const rightChartData1 = ref(); const rightChartData1 = ref();
@ -259,7 +257,6 @@ const state = reactive<Demo2State>({
earth3DBtnList, earth3DBtnList,
chartData4List, chartData4List,
myCharts: [], myCharts: [],
the3DEarth: null,
}); });
// 初始化时间 // 初始化时间
@ -690,13 +687,13 @@ const initRightChartData2 = () => {
}; };
// 3DEarth 地图 // 3DEarth 地图
const init3DEarth = (globeRadius: any) => { const init3DEarth = (globeRadius: any) => {
let el = state.the3DEarth!; let el = <HTMLElement>document.getElementById('3DEarth');
el.style.height = `${rightChartData5.value.offsetHeight}px`; el.style.height = `${rightChartData5.value.offsetHeight}px`;
const myChart = echarts.init(el); const myChart = echarts.init(el);
const option = { const option = {
globe: { globe: {
baseTexture: worldImg, baseTexture: 'https://i.hd-r.cn/4d572a171eb475da0c2e731d15b63aea.jpg',
heightTexture: bathymetryImg, heightTexture: 'https://i.hd-r.cn/52356e8d906c84c5e23390f829dec7a3.jpg',
shading: 'realistic', shading: 'realistic',
light: { light: {
ambient: { ambient: {
@ -791,7 +788,7 @@ onUnmounted(() => {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
background: url(https://img-blog.csdnimg.cn/6267533849444025811bf0840f9366e3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_20,color_FFFFFF,t_70,g_se,x_16); background: url(https://i.hd-r.cn/b1040178e4a2265fe87ffbb9bda839a4.jpg);
background-size: 100% 100%; background-size: 100% 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

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