34 Commits

Author SHA1 Message Date
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
98 changed files with 3364 additions and 4290 deletions

3
.env
View File

@ -4,5 +4,8 @@ VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false
# 打包是否开启 cdn源文件 utils/build.ts可自行修改
VITE_OPEN_CDN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
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,94 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 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
`2022.11.29`
@ -75,7 +163,7 @@
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue`
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/topBar/user.vue`
- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题
- 🎯 优化 [如果 url 中有中文等特殊字符,第一次切换该 tab 时 keep-alive 失效#I55JS7](https://gitee.com/lyt-top/vue-next-admin/issues/I55JS7),感谢[yuyong1566](https://gitee.com/yuyong1566)
- 🎯 优化 [wangEditor](https://www.wangeditor.com/) 更新到 v5[vue3 版本线上示例中 wangeditor 富文本编辑器 demo 实例,无法换行#I5565B](https://gitee.com/lyt-top/vue-next-admin/issues/I5565B),感谢@[jenchih](https://gitee.com/jenchih)

View File

@ -1,25 +1,31 @@
<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">
<a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
</a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus">
</a>
<a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-vue3.x-green" alt="vue">
</a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-%3E1.0.0-blue" alt="element plus">
</a>
<a href="https://www.tslang.cn/" target="_blank">
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a>
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a>
<a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a>
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
</a>
</p>
<p>&nbsp;</p>
</div>
#### 💝 长期赞助商
<a href="http://www.ccflow.org/" target="_blank">
<img src="./src/assets/ccflowRightNextAdmin.png" width="50%" height="70px">
</a>
#### 🌈 介绍
基于 vue3.x + CompositionAPI setup 语法糖 + typescript + vite + element plus + vue-router-next + pinia 技术适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
@ -28,6 +34,7 @@
- vue3.x 版本预览vue-next-admin<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-next-admin-preview/#/login</a>
- vue2.x 版本预览vue-prev-admin<a href="https://lyt-top.gitee.io/vue-prev-admin-preview/#/login" target="_blank">https://lyt-top.gitee.io/vue-prev-admin-preview/#/login</a>
- vue3.x + uni-app 商城 H5vue-next-admin-shop<a href="https://lyt-top.gitee.io/vue-next-admin-shop-preview" target="_blank">https://lyt-top.gitee.io/vue-next-admin-shop-preview</a>
#### 💒 代码仓库
@ -43,13 +50,15 @@
| Edge | Firefox | Chrome | Safari |
| --------- | ------------ | ----------- | ----------- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 72 | Safari ≥ 12 |
| Edge ≥ 88 | Firefox ≥ 78 | Chrome ≥ 87 | Safari ≥ 13 |
> 由于 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
# 克隆项目
@ -74,28 +83,15 @@ cnpm run build
#### 💯 学习交流加 QQ 群
> 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。
> 1 - 5 交流群已满,请加 vue-next-admin 交流群 6
- 1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
- 2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>
- 3 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=02EWb5P2JkP-8iwzaDadgFdxA0HSHPpn&jump_from=webapi">795345435</a>
- 4 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=0gTFO04WwkeZZ6R4lju6gucbeXHK-wNd&jump_from=webapi">736626228</a>
群号761442083
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">
<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>
其它交流群请查看文档首页 [vueNextAdmin 解疑问](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)
#### 💒 集成后端
- <a target="_blank" href="https://gitee.com/zuohuaijun/Admin.NET">@zuohuaijun Admin.NET</a>
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
- <a target="_blank" href="https://toscode.gitee.com/GionConnection/gopro_free">@甜蜜蜜 GoPro 平台</a>
- <a target="_blank" href="https://gitee.com/GionConnection/niupi-free">@甜蜜蜜 NiuPi 平台</a>
@ -116,7 +112,6 @@ cnpm run build
- <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/axios/axios" target="_blank">axios</a>
- <a href="https://github.com/zenorocha/clipboard.js" target="_blank">clipboard</a>
- <a href="https://github.com/inorganik/countUp.js" target="_blank">countUp</a>
- <a href="https://github.com/developit/mitt" target="_blank">mitt</a>
- <a href="https://github.com/rstacruz/nprogress" target="_blank">nprogress</a>
@ -127,12 +122,19 @@ cnpm run build
- <a href="https://github.com/vitejs/vite" target="_blank">vite</a>
- <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a>
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
- <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">qrcodejs</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/antoniandre/splitpanes" target="_blank">splitpanes</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/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>
#### 💕 特别感谢

4138
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +1,62 @@
{
"name": "vue-next-admin",
"version": "2.4.0",
"version": "2.4.33",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vite --force",
"dev": "vite",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@element-plus/icons-vue": "^2.1.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.2.0",
"countup.js": "^2.3.2",
"axios": "^1.3.5",
"countup.js": "^2.6.0",
"cropperjs": "^1.5.13",
"echarts": "^5.4.0",
"echarts": "^5.4.2",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.2.25",
"element-plus": "^2.3.3",
"js-cookie": "^3.0.1",
"js-table2excel": "^1.0.3",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.27",
"pinia": "^2.0.34",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"qs": "^6.11.1",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"vue": "^3.2.45",
"vue": "^3.2.47",
"vue-clipboard3": "^2.0.0",
"vue-demi": "^0.13.11",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@types/node": "^18.15.11",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vue/compiler-sfc": "^3.2.45",
"dotenv": "^16.0.3",
"eslint": "^8.28.0",
"eslint-plugin-vue": "^9.8.0",
"prettier": "^2.8.0",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-eslint-parser": "^9.1.0"
"@types/sortablejs": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"@vitejs/plugin-vue": "^4.1.0",
"@vue/compiler-sfc": "^3.2.47",
"eslint": "^8.38.0",
"eslint-plugin-vue": "^9.10.0",
"prettier": "^2.8.7",
"sass": "^1.61.0",
"typescript": "^5.0.4",
"vite": "^4.2.1",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
"vue-eslint-parser": "^9.1.1"
},
"browserslist": [
"> 1%",
@ -65,8 +67,8 @@
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
},
"engines": {
"node": ">=12.0.0",
"npm": ">= 6.0.0"
"node": ">=16.0.0",
"npm": ">= 7.0.0"
},
"keywords": [
"vue",

View File

@ -1,9 +1,11 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" />
<router-view v-show="setLockScreen" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<Setings ref="setingsRef" v-show="setLockScreen" />
<CloseFull v-if="!themeConfig.isLockScreen" />
<Upgrade v-if="getVersion" />
<Sponsors />
</el-config-provider>
</template>
@ -21,8 +23,10 @@ import setIntroduction from '/@/utils/setIconfont';
// 引入组件
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue'));
const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue'));
const Setings = defineAsyncComponent(() => import('/@/layout/navBars/topBar/setings.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();
@ -32,6 +36,21 @@ const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
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(() => {
return other.globalComponentSize();

View File

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

View File

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

View File

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

View File

@ -1,67 +1,48 @@
<template>
<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
placement="bottom"
:width="state.fontIconWidth"
trigger="click"
transition="el-zoom-in-top"
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>
<div class="icon-selector-warp">
<div class="icon-selector-warp-title flex">
<div class="flex-auto">{{ title }}</div>
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
<span :class="{ 'span-active': state.fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">
ali
</span>
<span :class="{ 'span-active': state.fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">
ele
</span>
<span :class="{ 'span-active': state.fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">
awe
</span>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': 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 class="icon-selector-warp-title">{{ title }}</div>
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<el-tab-pane lazy label="ali" name="ali">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="ele" name="ele">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="awe" name="awe">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
</el-tabs>
</div>
</template>
</el-popover>
@ -69,8 +50,10 @@
</template>
<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 '/@/theme/iconSelector.scss';
// 定义父组件传过来的值
const props = defineProps({
@ -94,11 +77,6 @@ const props = defineProps({
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
@ -123,18 +101,22 @@ const props = defineProps({
// 定义子组件向父组件传值/事件
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
// 引入组件
const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/list.vue'));
// 定义变量内容
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconWidth: 0,
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
fontIconTabActive: 'ali',
fontIconList: {
ali: [],
ele: [],
awe: [],
},
});
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
@ -145,32 +127,91 @@ const onIconFocus = () => {
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
const list = fontIconTabNameList();
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 = '';
}, 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 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
(<string | undefined>state.fontIconPrefix) = props.modelValue;
};
// 处理 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
const initFontIconTypeEcho = () => {
if (props.modelValue!.indexOf('iconfont') > -1) onIconChange('ali');
else if (props.modelValue!.indexOf('ele-') > -1) onIconChange('ele');
else if (props.modelValue!.indexOf('fa') > -1) onIconChange('awe');
else onIconChange('ali');
// 处理 icon 类型用于回显时tab 高亮与初始化数据
const initFontIconName = () => {
let name = 'ali';
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
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 的宽度
const getInputWidth = () => {
nextTick(() => {
@ -183,56 +224,9 @@ const initResize = () => {
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(() => {
initModeValueEcho();
initFontIconData(initFontIconName());
initResize();
getInputWidth();
});
@ -241,6 +235,7 @@ watch(
() => props.modelValue,
() => {
initModeValueEcho();
initFontIconName();
}
);
</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"
>
<template v-slot="scope">
<template v-if="item.key === 'image'">
<img :src="scope.row[item.key]" :width="item.width" :height="item.height" />
<template v-if="item.type === 'image'">
<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 v-else>
{{ scope.row[item.key] }}
@ -56,6 +63,7 @@
>
</el-pagination>
<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-shuaxin" :size="22" title="刷新" @click="onRefreshTable" />
<el-popover
@ -71,26 +79,28 @@
<SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
</template>
<template #default>
<div ref="toolSetRef">
<div class="tool-box">
<el-tooltip content="拖动进行排序" placement="top-start">
<SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
</el-tooltip>
<el-checkbox
v-model="state.checkListAll"
:indeterminate="state.checkListIndeterminate"
class="ml10 mr1"
label="列显示"
@change="onCheckAllChange"
/>
<el-checkbox v-model="getConfig.isSerialNo" 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 class="tool-box">
<el-tooltip content="拖动进行排序" placement="top-start">
<SvgIcon name="fa fa-question-circle-o" :size="17" class="ml11" color="#909399" />
</el-tooltip>
<el-checkbox
v-model="state.checkListAll"
:indeterminate="state.checkListIndeterminate"
class="ml10 mr1"
label="列显示"
@change="onCheckAllChange"
/>
<el-checkbox v-model="getConfig.isSerialNo" class="ml12 mr1" label="序号" />
<el-checkbox v-model="getConfig.isSelection" class="ml12 mr1" label="多选" />
</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>
</el-popover>
</div>
@ -101,6 +111,7 @@
<script setup lang="ts" name="netxTable">
import { reactive, computed, nextTick, ref } from 'vue';
import { ElMessage } from 'element-plus';
import printJs from 'print-js';
import table2excel from 'js-table2excel';
import Sortable from 'sortablejs';
import { storeToRefs } from 'pinia';
@ -119,16 +130,16 @@ const props = defineProps({
type: Array<EmptyObjectType>,
default: () => [],
},
// 搜索参数
param: {
type: Object,
default: () => {},
},
// 配置项
config: {
type: Object,
default: () => {},
},
// 打印标题
printName: {
type: String,
default: () => '',
},
});
// 定义子组件向父组件传值/事件
@ -161,7 +172,7 @@ const setHeader = computed(() => {
return props.header.filter((v) => v.isCheck);
});
// tool 列显示全选改变时
const onCheckAllChange = (val: boolean) => {
const onCheckAllChange = <T>(val: T) => {
if (val) props.header.forEach((v) => (v.isCheck = true));
else props.header.forEach((v) => (v.isCheck = false));
state.checkListIndeterminate = false;
@ -196,6 +207,37 @@ const pageReset = () => {
state.page.pageSize = 10;
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 = () => {
if (state.selectlist.length <= 0) return ElMessage.warning('请先选择要导出的数据');
@ -214,7 +256,7 @@ const onSetTable = () => {
animation: 150,
onEnd: () => {
const headerList: EmptyObjectType[] = [];
sortable.toArray().forEach((val) => {
sortable.toArray().forEach((val: string) => {
props.header.forEach((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 { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import enLocale from 'element-plus/lib/locale/lang/en';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
import 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 为框架的国际化内容
* /src/i18n/pages 下的 ts 为各界面的国际化内容
* 须在 pages 下新建文件夹(建议 `要国际化界面目录` 与 `i18n 目录` 相同,方便查找),
* 注意国际化定义的字段,不要与原有的定义字段相同。
* 1、/src/i18n/lang 下的 ts 为框架的国际化内容
* 2、/src/i18n/pages 下的 ts 为各界面的国际化内容
*/
const messages = {
[zhcnLocale.name]: {
...zhcnLocale,
message: {
...nextZhcn,
...pagesLoginZhcn,
...pagesFormI18nZhcn,
},
},
[enLocale.name]: {
...enLocale,
message: {
...nextEn,
...pagesLoginEn,
...pagesFormI18nEn,
},
},
[zhtwLocale.name]: {
...zhtwLocale,
message: {
...nextZhtw,
...pagesLoginZhtw,
...pagesFormI18nZhtw,
},
},
};
// element plus 自带国际化
import enLocale from 'element-plus/lib/locale/lang/en';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
// 定义变量内容
const messages = {};
const element = { en: enLocale, 'zh-cn': zhcnLocale, 'zh-tw': zhtwLocale };
const itemize = { en: [], 'zh-cn': [], 'zh-tw': [] };
const modules: Record<string, any> = import.meta.glob('./**/*.ts', { eager: true });
// 对自动引入的 modules 进行分类 en、zh-cn、zh-tw
// https://vitejs.cn/vite3-cn/guide/features.html#glob-import
for (const path in modules) {
const key = path.match(/(\S+)\/(\S+).ts/);
if (itemize[key![2]]) itemize[key![2]].push(modules[path].default);
else itemize[key![2]] = modules[path];
}
// 合并数组对象(非标准数组对象,数组中对象的每项 key、value 都不同)
function mergeArrObj<T>(list: T, key: string) {
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 默认语言
const stores = useThemeConfig(pinia);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -213,7 +213,7 @@ onUnmounted(() => {
}
.layout-lock-screen-img {
@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%;
z-index: 9999991;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { storeToRefs } from 'pinia';
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'));
// 定义变量内容

View File

@ -52,10 +52,10 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import Sortable from 'sortablejs';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useRoutesList } from '/@/stores/routesList';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
@ -72,8 +72,10 @@ const tagsUlRef = ref();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesRoutesList = useRoutesList();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
const { routesList } = storeToRefs(storesRoutesList);
const storesKeepALiveNames = useKeepALiveNames();
const route = useRoute();
const router = useRouter();
@ -382,6 +384,11 @@ const onMousedownMenu = (v: RouteItem, e: MouseEvent) => {
const onTagsClick = (v: RouteItem, k: number) => {
state.tagsRefsIndex = k;
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
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
@ -578,9 +585,9 @@ onBeforeRouteUpdate(async (to) => {
});
// 监听路由的变化,动态赋值给 tagsView
watch(
pinia.state,
() => tagsViewRoutes.value,
(val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
if (val.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
},
{

View File

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

View File

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

View File

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

View File

@ -1,37 +1,35 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-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" />
<span>{{ $t(val.meta.title) }}</span>
{{ $t(val.meta.title) }}
</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)">
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</a>
</template>
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
</template>
</el-menu>
</div>
</template>
<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 { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
@ -52,7 +50,6 @@ const props = defineProps({
});
// 定义变量内容
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores);
@ -66,19 +63,6 @@ const state = reactive({
const menuLists = computed(() => {
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[] => {
return arr
@ -122,10 +106,6 @@ const onALinkClick = (val: RouteItem) => {
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G

View File

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

View File

@ -2,7 +2,7 @@
<div class="layout-padding layout-padding-unset layout-iframe">
<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">
<transition-group :name="name" mode="out-in">
<transition-group :name="name">
<iframe
:src="v.meta.isLink"
:key="v.path"

View File

@ -80,9 +80,13 @@ onMounted(() => {
getIframeListRoutes();
// 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/pulls/40
nextTick(() => {
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);
});
});

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。右上角点star方可加群: 1060674395`,
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,151 @@
<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) {
.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 pinia from '/@/stores/index';
import App from './App.vue';
import router from './router';
import App from '/@/App.vue';
import router from '/@/router';
import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n/index';
import other from '/@/utils/other';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss';
import VueGridLayout from 'vue-grid-layout';
@ -16,4 +15,4 @@ const app = createApp(App);
directive(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();
// 获取路由菜单数据
const res = await getBackEndControlRoutes();
// 无登录权限时,添加判断
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
if (res.data.length <= 0) return Promise.resolve(true);
// 存储接口原始路由未处理component根据需求选择使用
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
// 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由
@ -50,7 +53,7 @@ export async function initBackEndControlRoutes() {
// 添加动态路由
await setAddRoute();
// 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
await setFilterMenuAndCacheTagsViewRoutes();
setFilterMenuAndCacheTagsViewRoutes();
}
/**
@ -58,7 +61,7 @@ export async function initBackEndControlRoutes() {
* @description 用于左侧菜单、横向菜单的显示
* @description 用于 tagsView、菜单搜索中未过滤隐藏的(isHide)
*/
export function setFilterMenuAndCacheTagsViewRoutes() {
export async function setFilterMenuAndCacheTagsViewRoutes() {
const storesRoutesList = useRoutesList(pinia);
storesRoutesList.setRoutesList(dynamicRoutes[0].children as any);
setCacheTagsViewRoutes();
@ -119,8 +122,8 @@ export function getBackEndControlRoutes() {
* @description 用于菜单管理界面刷新菜单(未进行测试)
* @description 路径:/src/views/system/menu/component/addMenu.vue
*/
export function setBackEndControlRefreshRoutes() {
getBackEndControlRoutes();
export async function setBackEndControlRefreshRoutes() {
await getBackEndControlRoutes();
}
/**

View File

@ -26,10 +26,13 @@ export async function initFrontEndControlRoutes() {
// 触发初始化用户信息 pinia
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
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();
// 设置递归过滤有权限的路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
await setFilterMenuAndCacheTagsViewRoutes();
setFilterMenuAndCacheTagsViewRoutes();
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,64 +5,25 @@
.icon-selector-warp {
height: 260px;
overflow: hidden;
position: relative;
.icon-selector-warp-title {
position: absolute;
height: 40px;
line-height: 40px;
padding: 0 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;
}
}
left: 15px;
}
.icon-selector-warp-row {
height: 230px;
overflow: hidden;
border-top: 1px solid var(--el-border-color);
.el-row {
padding: 15px;
}
.el-scrollbar__bar.is-horizontal {
display: none;
}
.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);
}
.el-tabs__header {
display: flex;
justify-content: flex-end;
padding: 0 15px;
border-bottom: 1px solid var(--el-border-color-light);
margin: 0 !important;
.el-tabs__nav-wrap {
&::after {
height: 0 !important;
}
&:hover {
cursor: pointer;
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);
}
.el-tabs__item {
padding: 0 5px !important;
}
}
}

View File

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

View File

@ -16,4 +16,16 @@
// 响应式表单时,登录页需要重新处理
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 {
width: 80% !important;
}
// 锁屏页
.layout-lock-screen-date-top {
display: none;
}
}
/* 页面宽度小于768px

View File

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

View File

@ -5,6 +5,8 @@ declare module 'splitpanes';
declare module 'js-cookie';
declare module '@wangeditor/editor-for-vue';
declare module 'js-table2excel';
declare module 'qs';
declare module 'sortablejs';
// 声明一个模块,防止引入文件时报错
declare module '*.json';
@ -25,6 +27,8 @@ declare module '*.vue' {
/* eslint-disable */
declare interface Window {
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> {
userInfos: {
authBtnList: string[];
photo: string;
roles: string[];
time: number;
userName: string;
[key: string]: T;
};
declare interface UserInfos<T = any> {
authBtnList: string[];
photo: string;
roles: string[];
time: number;
userName: string;
[key: string]: T;
}
declare interface UserInfosState {
userInfos: UserInfos;
}
// 路由缓存列表
@ -48,6 +49,7 @@ declare interface ThemeConfigState {
isTopBarColorGradual: boolean;
menuBar: string;
menuBarColor: string;
menuBarActiveColor: string;
isMenuBarColorGradual: boolean;
columnsMenuBar: string;
columnsMenuBarColor: string;

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

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

@ -53,8 +53,11 @@ export function useTitle() {
export function setTagsViewNameI18n(item: any) {
let tagsViewName: string = '';
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 (/\/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));
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 { Session } from '/@/utils/storage';
import qs from 'qs';
// 配置新建一个 axios 实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
paramsSerializer: {
serialize(params) {
return qs.stringify(params, { allowDots: true });
},
},
});
// 添加请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
(config) => {
// 在发送请求之前做些什么 token
if (Session.get('token')) {
config.headers!['Authorization'] = `${Session.get('token')}`;
@ -40,7 +46,7 @@ service.interceptors.response.use(
}
return Promise.reject(service.interceptors.response);
} else {
return response.data;
return res;
}
},
(error) => {

View File

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

View File

@ -13,9 +13,7 @@
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<img src="https://i.hd-r.cn/2cf0d2e192660eec23eb9d0655753e7d.png" />
</div>
</div>
</div>

View File

@ -13,9 +13,7 @@
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<img src="https://i.hd-r.cn/1a0d90a6c1e8b0184c7299dda713effd.png" />
</div>
</div>
</div>
@ -28,6 +26,7 @@ import { useRouter } from 'vue-router';
// 定义变量内容
const router = useRouter();
// 返回首页
const onGoHome = () => {
router.push('/');
};

View File

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

View File

@ -66,7 +66,7 @@
</template>
<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 { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
@ -192,7 +192,7 @@ const state = reactive({
// 折线图
const initLineChart = () => {
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 = {
backgroundColor: state.charts.bgColor,
title: {
@ -274,7 +274,7 @@ const initLineChart = () => {
// 饼图
const initPieChart = () => {
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 getvalue = [34.2, 38.87, 17.88, 9.05, 2.05];
var data = [];
@ -359,7 +359,7 @@ const initPieChart = () => {
// 柱状图
const initBarChart = () => {
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 = {
backgroundColor: state.charts.bgColor,
title: {

View File

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

View File

@ -100,37 +100,41 @@ const onSignIn = async () => {
Cookies.set('userName', state.ruleForm.userName);
if (!themeConfig.value.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序
await initFrontEndControlRoutes();
signInSuccess();
const isNoPower = await initFrontEndControlRoutes();
signInSuccess(isNoPower);
} else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
await initBackEndControlRoutes();
const isNoPower = await initBackEndControlRoutes();
// 执行完 initBackEndControlRoutes再执行 signInSuccess
signInSuccess();
signInSuccess(isNoPower);
}
};
// 登录成功后的跳转
const signInSuccess = () => {
// 初始化登录成功时间问候语
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) : '',
});
const signInSuccess = (isNoPower: boolean | undefined) => {
if (isNoPower) {
ElMessage.warning('抱歉,您没有登录权限');
Session.clear();
} 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();
}
// 登录成功提示
// 关闭 loading
state.loading.signIn = true;
const signInText = t('message.signInText');
ElMessage.success(`${currentTimeInfo}${signInText}`);
// 添加 loading防止第一次进入界面时出现短暂空白
NextLoading.start();
state.loading.signIn = false;
};
</script>

View File

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

View File

@ -1,15 +1,7 @@
<template>
<div class="table-demo-container layout-padding">
<div class="table-demo-padding layout-padding-view layout-padding-auto">
<div class="system-user-search mb15">
<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>
<TableSearch :search="state.tableData.search" @search="onSearch" />
<Table
ref="tableRef"
v-bind="state.tableData"
@ -28,6 +20,7 @@ import { ElMessage } from 'element-plus';
// 引入组件
const Table = defineAsyncComponent(() => import('/@/components/table/index.vue'));
const TableSearch = defineAsyncComponent(() => import('/@/views/make/tableDemo/search.vue'));
// 定义变量内容
const tableRef = ref<RefType>();
@ -44,10 +37,6 @@ const state = reactive<TableDemoState>({
{ key: 'isSupport', colWidth: '', title: '是否支持24小时核酸检测', type: 'text', isCheck: true },
{ key: 'image', colWidth: '', width: '70', height: '40', title: '图片描述', type: 'image', isCheck: true },
],
// 搜索参数(可选)
param: {
search: '',
},
// 配置项(必传)
config: {
total: 0, // 列表总数
@ -57,6 +46,33 @@ const state = reactive<TableDemoState>({
isSelection: 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;
}, 500);
};
// 搜索
const onSearch = () => {
// 搜索点击时表单回调
const onSearch = (data: EmptyObjectType) => {
state.tableData.param = Object.assign({}, state.tableData.param, { ...data });
tableRef.value.pageReset();
};
// 删除当前项回调
@ -100,7 +117,6 @@ const onTablePageChange = (page: TableDemoPageType) => {
const onSortHeader = (data: TableHeaderType[]) => {
state.tableData.header = data;
};
// 页面加载时
onMounted(() => {
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)"
:class="{ 'min-h-360': state.tableData.data.length <= 0 }"
>
<div
v-for="(val, key) in filtering"
:key="key"
:ref="
(el) => {
if (el) dlRefs[key] = el;
}
"
class="filtering-list-flex"
>
<div v-for="(val, key) in state.filtering" :key="key" ref="dlRefs" class="filtering-list-flex">
<div class="filtering-list-title">{{ val.title }}</div>
<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)">{{
v.label
}}</span>
<span class="span" :class="v.active ? 'dd-active' : ''" v-for="(v, k) in val.children" :key="k" @click="onSelItem(val, v)">
{{ v.label }}
</span>
<div class="dd-more" v-if="val.isShowMore" @click="val.isMore = !val.isMore">
<span>{{ val.isMore ? '收起' : '展开' }}</span>
<i :class="val.isMore ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
@ -235,6 +226,9 @@ const onHandleCurrentChange = (val: number) => {
flex-wrap: wrap;
align-content: flex-start;
margin: 0 -5px;
.el-row {
width: 101%;
}
.flex-warp-item {
padding: 5px;
width: 100%;

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
<el-scrollbar>
<div
ref="leftNavRefs"
v-for="val in leftNavList"
v-for="val in state.leftNavList"
:key="val.id"
:style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
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 leftNavRefs = ref([]);
const workflowRightRef = ref();
const contextmenuNodeRef = ref();
const contextmenuLineRef = ref();
const drawerRef = ref();
@ -95,8 +97,6 @@ const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
const state = reactive<WorkflowState>({
workflowRightRef: null,
leftNavRefs: [],
leftNavList: [],
dropdownNode: { x: '', y: '' },
dropdownLine: { x: '', y: '' },
@ -151,7 +151,7 @@ const initLeftNavList = () => {
};
// 左侧导航-初始化拖动
const initSortable = () => {
state.leftNavRefs.forEach((v) => {
leftNavRefs.value.forEach((v) => {
Sortable.create(v as HTMLDivElement, {
group: {
name: 'vue-next-admin-1',
@ -165,7 +165,7 @@ const initSortable = () => {
onEnd: function (evt: any) {
const { name, icon, id } = evt.clone.dataset;
const { layerX, layerY, clientX, clientY } = evt.originalEvent;
const el = state.workflowRightRef!;
const el = workflowRightRef.value!;
const { x, y, width, height } = el.getBoundingClientRect();
if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
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>
<div class="system-edit-dept-container">
<el-dialog title="修改部门" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<div class="system-dept-dialog-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form ref="deptDialogFormRef" :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="上级部门">
@ -60,19 +60,22 @@
<template #footer>
<span class="dialog-footer">
<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>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemEditDept">
import { reactive, onMounted } from 'vue';
<script setup lang="ts" name="systemDeptDialog">
import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
//
const deptDialogFormRef = ref();
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [] as string[], //
deptName: '', //
@ -84,31 +87,51 @@ const state = reactive({
describe: '', //
},
deptData: [] as DeptTreeType[], //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
});
//
const openDialog = (row: RowDeptType) => {
row.deptLevel = ['vueNextAdmin'];
row.person = 'lyt';
row.phone = '12345678910';
row.email = 'vueNextAdmin@123.com';
state.ruleForm = row;
state.isShowDialog = true;
const openDialog = (type: string, row: RowDeptType) => {
if (type === 'edit') {
row.deptLevel = ['vueNextAdmin'];
row.person = 'lyt';
row.phone = '12345678910';
row.email = 'vueNextAdmin@123.com';
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 = () => {
state.isShowDialog = false;
state.dialog.isShowDialog = false;
};
//
const onCancel = () => {
closeDialog();
};
//
//
const onSubmit = () => {
closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
};
//
const initTableData = () => {
const getMenuData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
@ -136,10 +159,6 @@ const initTableData = () => {
],
});
};
//
onMounted(() => {
initTableData();
});
//
defineExpose({

View File

@ -9,7 +9,7 @@
</el-icon>
查询
</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>
<ele-FolderAdd />
</el-icon>
@ -18,6 +18,7 @@
</div>
<el-table
:data="state.tableData.data"
v-loading="state.tableData.loading"
style="width: 100%"
row-key="id"
default-expand-all
@ -39,15 +40,14 @@
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenAddDept">新增</el-button>
<el-button size="small" text type="primary" @click="onOpenEditDept(scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onOpenAddDept('add')">新增</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>
</template>
</el-table-column>
</el-table>
</el-card>
<AddDept ref="addDeptRef" />
<EditDept ref="editDeptRef" />
<DeptDialog ref="deptDialogRef" @refresh="getTableData()" />
</div>
</template>
@ -56,12 +56,10 @@ import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件
const AddDept = defineAsyncComponent(() => import('/@/views/system/dept/component/addDept.vue'));
const EditDept = defineAsyncComponent(() => import('/@/views/system/dept/component/editDept.vue'));
const DeptDialog = defineAsyncComponent(() => import('/@/views/system/dept/dialog.vue'));
// 定义变量内容
const addDeptRef = ref();
const editDeptRef = ref();
const deptDialogRef = ref();
const state = reactive<SysDeptState>({
tableData: {
data: [],
@ -75,7 +73,9 @@ const state = reactive<SysDeptState>({
});
// 初始化表格数据
const initTableData = () => {
const getTableData = () => {
state.tableData.loading = true;
state.tableData.data = [];
state.tableData.data.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
@ -103,14 +103,17 @@ const initTableData = () => {
],
});
state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
};
// 打开新增菜单弹窗
const onOpenAddDept = () => {
addDeptRef.value.openDialog();
const onOpenAddDept = (type: string) => {
deptDialogRef.value.openDialog(type);
};
// 打开编辑菜单弹窗
const onOpenEditDept = (row: DeptTreeType) => {
editDeptRef.value.openDialog(row);
const onOpenEditDept = (type: string, row: DeptTreeType) => {
deptDialogRef.value.openDialog(type, row);
};
// 删除当前行
const onTabelRowDel = (row: DeptTreeType) => {
@ -120,12 +123,13 @@ const onTabelRowDel = (row: DeptTreeType) => {
type: 'warning',
})
.then(() => {
getTableData();
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 页面加载时
onMounted(() => {
initTableData();
getTableData();
});
</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>
<div class="system-edit-dic-container">
<el-dialog title="修改字典" v-model="state.isShowDialog" width="769px">
<div class="system-dic-dialog-container">
<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-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-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字典名称">
@ -56,19 +56,22 @@
<template #footer>
<span class="dialog-footer">
<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>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemEditDic">
import { reactive } from 'vue';
<script setup lang="ts" name="systemDicDialog">
import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
//
const dicDialogFormRef = ref();
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', //
fieldName: '', //
@ -76,37 +79,56 @@ const state = reactive({
list: [] as ListType[], // +
describe: '', //
},
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
});
//
const openDialog = (row: RowDicType) => {
if (row.fieldName === 'SYS_UERINFO') {
row.list = [
{ id: Math.random(), label: 'sex', value: '1' },
{ id: Math.random(), label: 'sex', value: '0' },
];
const openDialog = (type: string, row: RowDicType) => {
if (type === 'edit') {
if (row.fieldName === 'SYS_UERINFO') {
row.list = [
{ 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 {
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.dialog.title = '新增字典';
state.dialog.submitTxt = '新 增';
// 使
// nextTick(() => {
// dicDialogFormRef.value.resetFields();
// });
}
state.ruleForm = row;
state.isShowDialog = true;
state.dialog.isShowDialog = true;
};
//
const closeDialog = () => {
state.isShowDialog = false;
state.dialog.isShowDialog = false;
};
//
const onCancel = () => {
closeDialog();
};
//
//
const onSubmit = () => {
closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
};
//
const onAddRow = () => {

View File

@ -9,14 +9,14 @@
</el-icon>
查询
</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>
<ele-FolderAdd />
</el-icon>
新增字典
</el-button>
</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 prop="dicName" 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 label="操作" width="100">
<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>
</template>
</el-table-column>
@ -49,8 +49,7 @@
>
</el-pagination>
</el-card>
<AddDic ref="addDicRef" />
<EditDic ref="editDicRef" />
<DicDialog ref="dicDialogRef" @refresh="getTableData()" />
</div>
</template>
@ -59,12 +58,10 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件
const AddDic = defineAsyncComponent(() => import('/@/views/system/dic/component/addDic.vue'));
const EditDic = defineAsyncComponent(() => import('/@/views/system/dic/component/editDic.vue'));
const DicDialog = defineAsyncComponent(() => import('/@/views/system/dic/dialog.vue'));
// 定义变量内容
const addDicRef = ref();
const editDicRef = ref();
const dicDialogRef = ref();
const state = reactive<SysDicState>({
tableData: {
data: [],
@ -78,7 +75,8 @@ const state = reactive<SysDicState>({
});
// 初始化表格数据
const initTableData = () => {
const getTableData = () => {
state.tableData.loading = true;
const data = [];
for (let i = 0; i < 2; i++) {
data.push({
@ -92,14 +90,17 @@ const initTableData = () => {
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
};
// 打开新增字典弹窗
const onOpenAddDic = () => {
addDicRef.value.openDialog();
const onOpenAddDic = (type: string) => {
dicDialogRef.value.openDialog(type);
};
// 打开修改字典弹窗
const onOpenEditDic = (row: RowDicType) => {
editDicRef.value.openDialog(row);
const onOpenEditDic = (type: string, row: RowDicType) => {
dicDialogRef.value.openDialog(type, row);
};
// 删除字典
const onRowDel = (row: RowDicType) => {
@ -109,6 +110,7 @@ const onRowDel = (row: RowDicType) => {
type: 'warning',
})
.then(() => {
getTableData();
ElMessage.success('删除成功');
})
.catch(() => {});
@ -116,13 +118,15 @@ const onRowDel = (row: RowDicType) => {
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
getTableData();
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
getTableData();
};
// 页面加载时
onMounted(() => {
initTableData();
getTableData();
});
</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>
<div class="system-add-menu-container">
<el-dialog title="新增菜单" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="80px">
<div class="system-menu-dialog-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form ref="menuDialogFormRef" :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="上级菜单">
@ -51,12 +51,12 @@
</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" />
<IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" />
</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-input v-model="state.ruleForm.componentAlias" placeholder="组件路径" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@ -138,34 +138,38 @@
<template #footer>
<span class="dialog-footer">
<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>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemAddMenu">
import { defineAsyncComponent, reactive, onMounted } from 'vue';
<script setup lang="ts" name="systemMenuDialog">
import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { i18n } from '/@/i18n/index';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
// /
const emit = defineEmits(['refresh']);
//
const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelector/index.vue'));
//
const menuDialogFormRef = ref();
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);
const state = reactive({
isShowDialog: false,
// `/src/router/route.ts` `dynamicRoutes`
ruleForm: {
menuSuperior: [], //
menuType: 'menu', //
name: '', //
component: '', //
componentAlias: '', //
isLink: false, //
menuSort: 0, //
path: '', //
@ -183,6 +187,12 @@ const state = reactive({
btnPower: '', //
},
menuData: [] as RouteItems, //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
});
// pinia
@ -196,12 +206,28 @@ const getMenuData = (routes: RouteItems) => {
return arr;
};
//
const openDialog = () => {
state.isShowDialog = true;
const openDialog = (type: string, row?: any) => {
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 = () => {
state.isShowDialog = false;
state.dialog.isShowDialog = false;
};
//
const onSelectIframeChange = () => {
@ -212,9 +238,11 @@ const onSelectIframeChange = () => {
const onCancel = () => {
closeDialog();
};
//
//
const onSubmit = () => {
closeDialog(); //
emit('refresh');
// if (state.dialog.type === 'add') { }
// setBackEndControlRefreshRoutes() //
};
//

View File

@ -16,7 +16,13 @@
新增菜单
</el-button>
</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>
<template #default="scope">
<SvgIcon :name="scope.row.meta.icon" />
@ -46,46 +52,54 @@
</el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenAddMenu">新增</el-button>
<el-button size="small" text type="primary" @click="onOpenEditMenu(scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onOpenAddMenu('add')">新增</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>
</template>
</el-table-column>
</el-table>
</el-card>
<AddMenu ref="addMenuRef" />
<EditMenu ref="editMenuRef" />
<MenuDialog ref="menuDialogRef" @refresh="getTableData()" />
</div>
</template>
<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 { ElMessageBox, ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
// 引入组件
const AddMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/addMenu.vue'));
const EditMenu = defineAsyncComponent(() => import('/@/views/system/menu/component/editMenu.vue'));
const MenuDialog = defineAsyncComponent(() => import('/@/views/system/menu/dialog.vue'));
// 定义变量内容
const stores = useRoutesList();
const { routesList } = storeToRefs(stores);
const addMenuRef = ref();
const editMenuRef = ref();
// 获取 pinia 中的路由
const menuTableData = computed(() => {
return routesList.value;
const menuDialogRef = ref();
const state = reactive({
tableData: {
data: [] as RouteRecordRaw[],
loading: true,
},
});
// 获取路由数据,真实请从接口获取
const getTableData = () => {
state.tableData.loading = true;
state.tableData.data = routesList.value;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
};
// 打开新增菜单弹窗
const onOpenAddMenu = () => {
addMenuRef.value.openDialog();
const onOpenAddMenu = (type: string) => {
menuDialogRef.value.openDialog(type);
};
// 打开编辑菜单弹窗
const onOpenEditMenu = (row: RouteRecordRaw) => {
editMenuRef.value.openDialog(row);
const onOpenEditMenu = (type: string, row: RouteRecordRaw) => {
menuDialogRef.value.openDialog(type, row);
};
// 删除当前行
const onTabelRowDel = (row: RouteRecordRaw) => {
@ -96,7 +110,13 @@ const onTabelRowDel = (row: RouteRecordRaw) => {
})
.then(() => {
ElMessage.success('删除成功');
getTableData();
//await setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
})
.catch(() => {});
};
// 页面加载时
onMounted(() => {
getTableData();
});
</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>
<div class="system-add-role-container">
<el-dialog title="新增角色" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<div class="system-role-dialog-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form ref="roleDialogFormRef" :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="角色名称">
@ -43,19 +43,22 @@
<template #footer>
<span class="dialog-footer">
<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>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemAddRole">
import { reactive } from 'vue';
<script setup lang="ts" name="systemRoleDialog">
import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
//
const roleDialogFormRef = ref();
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', //
roleSign: '', //
@ -68,24 +71,44 @@ const state = reactive({
children: 'children',
label: 'label',
},
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
});
//
const openDialog = () => {
state.isShowDialog = true;
const openDialog = (type: string, row: RowRoleType) => {
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();
};
//
const closeDialog = () => {
state.isShowDialog = false;
state.dialog.isShowDialog = false;
};
//
const onCancel = () => {
closeDialog();
};
//
//
const onSubmit = () => {
closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
};
//
const getMenuData = () => {
@ -202,7 +225,7 @@ defineExpose({
</script>
<style scoped lang="scss">
.system-add-role-container {
.system-role-dialog-container {
.menu-data-tree {
width: 100%;
border: 1px solid var(--el-border-color);

View File

@ -9,14 +9,14 @@
</el-icon>
查询
</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>
<ele-FolderAdd />
</el-icon>
新增角色
</el-button>
</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 prop="roleName" 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 label="操作" width="100">
<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 :disabled="scope.row.roleName === '超级管理员'" size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
@ -52,8 +52,7 @@
>
</el-pagination>
</div>
<AddRole ref="addRoleRef" />
<EditRole ref="editRoleRef" />
<RoleDialog ref="roleDialogRef" @refresh="getTableData()" />
</div>
</template>
@ -62,12 +61,10 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
// 引入组件
const AddRole = defineAsyncComponent(() => import('/@/views/system/role/component/addRole.vue'));
const EditRole = defineAsyncComponent(() => import('/@/views/system/role/component/editRole.vue'));
const RoleDialog = defineAsyncComponent(() => import('/@/views/system/role/dialog.vue'));
// 定义变量内容
const addRoleRef = ref();
const editRoleRef = ref();
const roleDialogRef = ref();
const state = reactive<SysRoleState>({
tableData: {
data: [],
@ -81,7 +78,8 @@ const state = reactive<SysRoleState>({
},
});
// 初始化表格数据
const initTableData = () => {
const getTableData = () => {
state.tableData.loading = true;
const data = [];
for (let i = 0; i < 20; i++) {
data.push({
@ -95,14 +93,17 @@ const initTableData = () => {
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
setTimeout(() => {
state.tableData.loading = false;
}, 500);
};
// 打开新增角色弹窗
const onOpenAddRole = () => {
addRoleRef.value.openDialog();
const onOpenAddRole = (type: string) => {
roleDialogRef.value.openDialog(type);
};
// 打开修改角色弹窗
const onOpenEditRole = (row: Object) => {
editRoleRef.value.openDialog(row);
const onOpenEditRole = (type: string, row: Object) => {
roleDialogRef.value.openDialog(type, row);
};
// 删除角色
const onRowDel = (row: RowRoleType) => {
@ -112,6 +113,7 @@ const onRowDel = (row: RowRoleType) => {
type: 'warning',
})
.then(() => {
getTableData();
ElMessage.success('删除成功');
})
.catch(() => {});
@ -119,14 +121,16 @@ const onRowDel = (row: RowRoleType) => {
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
getTableData();
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
getTableData();
};
// 页面加载时
onMounted(() => {
initTableData();
getTableData();
});
</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>
<div class="system-edit-user-container">
<el-dialog title="修改用户" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="default" label-width="90px">
<div class="system-user-dialog-container">
<el-dialog :title="state.dialog.title" v-model="state.dialog.isShowDialog" width="769px">
<el-form ref="userDialogFormRef" :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="账户名称">
@ -81,19 +81,22 @@
<template #footer>
<span class="dialog-footer">
<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>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="systemEditUser">
import { reactive, onMounted } from 'vue';
<script setup lang="ts" name="systemUserDialog">
import { reactive, ref } from 'vue';
// /
const emit = defineEmits(['refresh']);
//
const userDialogFormRef = ref();
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', //
userNickname: '', //
@ -108,27 +111,47 @@ const state = reactive({
describe: '', //
},
deptData: [] as DeptTreeType[], //
dialog: {
isShowDialog: false,
type: '',
title: '',
submitTxt: '',
},
});
//
const openDialog = (row: RowUserType) => {
state.ruleForm = row;
state.isShowDialog = true;
const openDialog = (type: string, row: RowUserType) => {
if (type === 'edit') {
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 = () => {
state.isShowDialog = false;
state.dialog.isShowDialog = false;
};
//
const onCancel = () => {
closeDialog();
};
//
//
const onSubmit = () => {
closeDialog();
emit('refresh');
// if (state.dialog.type === 'add') { }
};
//
const initTableData = () => {
const getMenuData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
@ -156,10 +179,6 @@ const initTableData = () => {
],
});
};
//
onMounted(() => {
initTableData();
});
//
defineExpose({

View File

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

View File

@ -126,7 +126,7 @@ const initTime = () => {
const convertData = (data: any) => {
let res = [];
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) {
res.push({
name: data[i].name,
@ -250,16 +250,16 @@ const initEchartsMap = () => {
// BMAP_SATELLITE_MAP卫星地图 (没有坐标, 绿绿的一片的卫星地图)
// BMAP_HYBRID_MAP混合地图 (既有坐标,也是绿绿的一片的卫星地图)
// eslint-disable-next-line no-undef
map.setMapType(BMAP_SATELLITE_MAP);
map.setMapType(window.BMAP_SATELLITE_MAP);
// eslint-disable-next-line no-undef
let bdary = new BMap.Boundary();
let bdary = new window.BMap.Boundary();
// 获取行政区域
bdary.get('深圳', function (rs: any) {
// 行政区域的点有多少个
let count = rs.boundaries.length;
for (let i = 0; i < count; i++) {
// 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,
// 设置多边形边线透明度0-1
@ -280,7 +280,7 @@ const initEchartsMap = () => {
// 初始化地图,设置中心点坐标和地图级别
// new BMap.Point('深圳市', 11)
// 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-one">
<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="circle" v-for="i in 4" :key="i"></div>
<div class="text-box">
@ -235,8 +235,6 @@ import 'echarts-gl';
import { formatDate } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading';
import { dropdownList, skyList, dBtnList, earth3DBtnList, chartData4List } from './mock/demo2';
import worldImg from './images/world.jpg';
import bathymetryImg from './images/bathymetry.jpg';
// 定义变量内容
const rightChartData1 = ref();
@ -259,7 +257,6 @@ const state = reactive<Demo2State>({
earth3DBtnList,
chartData4List,
myCharts: [],
the3DEarth: null,
});
// 初始化时间
@ -690,13 +687,13 @@ const initRightChartData2 = () => {
};
// 3DEarth 地图
const init3DEarth = (globeRadius: any) => {
let el = state.the3DEarth!;
let el = <HTMLElement>document.getElementById('3DEarth');
el.style.height = `${rightChartData5.value.offsetHeight}px`;
const myChart = echarts.init(el);
const option = {
globe: {
baseTexture: worldImg,
heightTexture: bathymetryImg,
baseTexture: 'https://i.hd-r.cn/4d572a171eb475da0c2e731d15b63aea.jpg',
heightTexture: 'https://i.hd-r.cn/52356e8d906c84c5e23390f829dec7a3.jpg',
shading: 'realistic',
light: {
ambient: {
@ -791,7 +788,7 @@ onUnmounted(() => {
height: 100%;
width: 100%;
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%;
display: flex;
flex-direction: column;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 KiB

View File

@ -67,8 +67,7 @@
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"suppressImplicitAnyIndexErrors": true
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts"], // **Represents any directory, and * represents any file. Indicates that all files in the src directory will be compiled
"exclude": ["node_modules", "dist"] // Indicates the file directory that does not need to be compiled

View File

@ -1,9 +1,11 @@
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import { defineConfig, loadEnv, ConfigEnv } from 'vite';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';
import viteCompression from 'vite-plugin-compression';
import { buildConfig } from './src/utils/build';
const pathResolve = (dir: string): any => {
const pathResolve = (dir: string) => {
return resolve(__dirname, '.', dir);
};
@ -15,18 +17,16 @@ const alias: Record<string, string> = {
const viteConfig = defineConfig((mode: ConfigEnv) => {
const env = loadEnv(mode.mode, process.cwd());
return {
plugins: [vue(), vueSetupExtend()],
plugins: [vue(), vueSetupExtend(), viteCompression(), JSON.parse(env.VITE_OPEN_CDN) ? buildConfig.cdn() : null],
root: process.cwd(),
resolve: { alias },
base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
hmr: true,
optimizeDeps: {
include: ['element-plus/lib/locale/lang/zh-cn', 'element-plus/lib/locale/lang/en', 'element-plus/lib/locale/lang/zh-tw'],
},
optimizeDeps: { exclude: ['vue-demi'] },
server: {
host: '0.0.0.0',
port: env.VITE_PORT as unknown as number,
open: env.VITE_OPEN,
open: JSON.parse(env.VITE_OPEN),
hmr: true,
proxy: {
'/gitee': {
target: 'https://gitee.com',
@ -38,19 +38,19 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
},
build: {
outDir: 'dist',
sourcemap: false,
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
entryFileNames: `assets/[name].[hash].js`,
chunkFileNames: `assets/[name].[hash].js`,
assetFileNames: `assets/[name].[hash].[ext]`,
compact: true,
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
echarts: ['echarts'],
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().match(/\/node_modules\/(?!.pnpm)(?<moduleName>[^\/]*)\//)?.groups!.moduleName ?? 'vender';
}
},
},
...(JSON.parse(env.VITE_OPEN_CDN) ? { external: buildConfig.external } : {}),
},
},
css: { preprocessorOptions: { css: { charset: false } } },
@ -58,6 +58,8 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
__VUE_I18N_LEGACY_API__: JSON.stringify(false),
__VUE_I18N_FULL_INSTALL__: JSON.stringify(false),
__INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false),
__NEXT_VERSION__: JSON.stringify(process.env.npm_package_version),
__NEXT_NAME__: JSON.stringify(process.env.npm_package_name),
},
};
});