Compare commits

..

10 Commits

Author SHA1 Message Date
hxd
a5a74a2ead 地理分布,国外分布,国内分布,识别特征 2025-08-08 16:44:18 +08:00
hxd
0f7c42b949 分类,物种 2025-08-07 18:40:43 +08:00
hxd
575c278572 项目初始化 2025-08-06 18:44:13 +08:00
yxh
012025080d fix 更新element-plus致2.9.0,定时任务详情报错修复 2025-07-15 14:47:34 +08:00
yxh
68648f2a86 fix 完善handleTree方法 2025-06-25 21:41:53 +08:00
yxh
9deae50ccc fix 字典分类内滚动 2025-06-11 17:40:21 +08:00
yxh
8a38eaa7d6 fix 代码生成细节修复 2025-06-11 09:34:32 +08:00
yxh
02d67c7ac3 fix 字典树形菜单点击不勾选 2025-06-10 15:16:34 +08:00
yxh
3a7cf77d7b fix 字典数据类型选择功能修复 2025-06-10 14:36:31 +08:00
yxh
bb39c4bfd9 fix 更新依赖,修改字典页面布局 2025-06-10 09:59:54 +08:00
68 changed files with 10158 additions and 7738 deletions

View File

@ -37,7 +37,7 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-redeclare': 'error', '@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-unused-vars': [2], '@typescript-eslint/no-unused-vars': 'warning',
'vue/custom-event-name-casing': 'off', 'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off', 'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off', 'vue/one-component-per-file': 'off',

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
// 一行最多多少个字符 // 一行最多多少个字符
printWidth: 150, printWidth: 100,
// 指定每个缩进级别的空格数 // 指定每个缩进级别的空格数
tabWidth: 2, tabWidth: 2,
// 使用制表符而不是空格缩进行 // 使用制表符而不是空格缩进行

View File

@ -1,367 +0,0 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.2.0
`2022.07.10`
⚡⚡⚡ [/sec/stores/userInfo.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/stores/userInfo.ts) 下添加了 `getApiUserInfo` 接口模拟数据 `setTimeout` 为 3 秒
- 🌟 更新 依赖更新最新版本
- 🐞 修复 [主界面重新授权按钮点击卡死不跳转登录界面#I5C3JS](https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS),感谢[@Hero-Typ](https://gitee.com/tian_yu_peng)
- 🐞 修复 编译警告[#I5CVSB](https://gitee.com/lyt-top/vue-next-admin/issues/I5CVSB),全局替换成 `:deep(attr)`,感谢[@Linvas](https://gitee.com/linvas)。参考文档:[vue3 sfc-style](https://v3.cn.vuejs.org/api/sfc-style.html#style-scoped)。`node_modules\print-js\dist\print.js``print-js` 作者适配或去除 `package.json` 中的 `"print-js": "^1.6.0"`
- 🐞 修复 [vue-next-admin-template-js 版本前端控制路由userInfo.js 请求用户信息接口报错,加载不到路由 可以写个定时器模拟一下接口 一样的报错#I5F1HP](https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP),感谢[@白开水](https://gitee.com/libin951223)
## 2.1.1
`2022.05.27`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 深色模式下,`<el-button text></el-button>` 时,`:active` 样式
- 🎯 优化 [页面缓存在刷新之后失效 #I58U75](https://gitee.com/lyt-top/vue-next-admin/issues/I58U75)),感谢[@ls0428](https://gitee.com/ls0428)
- 🎯 优化 [SvgIcon 对下载的 Svg 图像设置颜色无效 #I59ND0](https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0)),感谢[@elus_z](https://gitee.com/elus_z)
- 🎯 优化 `/src/utils/toolsValidate.ts` 工具类
- 🐞 修复 [布局切换TagsView 显示的 tab 会多一个出来 #I58WGM](https://gitee.com/lyt-top/vue-next-admin/issues/I58WGM),感谢[@lg_boy](https://gitee.com/lg_boy)
- 🐞 修复 [如果设置顶部面包屑导航开启图标 isBreadcrumbIcon=true 后,样式有点问题 如果不开启就是正常的 #I58VB8](https://gitee.com/lyt-top/vue-next-admin/issues/I58VB8)
- 🐞 修复 地址栏路由地址输入错误时,返回首页后,再次输入路由地址错误时,不跳转 404 问题
- 🐞 修复 [2.1.0 版本的图标选择组件多次点击后功能失效 #I590TH](https://gitee.com/lyt-top/vue-next-admin/issues/I590TH),感谢[@quber](https://gitee.com/quber)
## 2.1.0
`2022.04.18`
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。因为 `vuex` 替换成 `pinia`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 部分界面图片不显示问题(更换 gitee 在线图片地址源)
- 🎯 优化 各界面方法引入与逻辑之间添加一行空行,方便区分内容
- 🎯 优化 图标选择器 [#I4YAHB](https://gitee.com/lyt-top/vue-next-admin/issues/I4YAHB),感谢[@真有你的](https://gitee.com/sunliusen)
- 🎯 优化 图标选择器 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
- 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts)
- 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白
- 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue`
- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线、全屏时关闭按钮消失问题
- 🎯 优化 [如果 url 中有中文等特殊字符,第一次切换该 tab 时 keep-alive 失效#I55JS7](https://gitee.com/lyt-top/vue-next-admin/issues/I55JS7),感谢[yuyong1566](https://gitee.com/yuyong1566)
- 🎯 优化 [wangEditor](https://www.wangeditor.com/) 更新到 v5[vue3 版本线上示例中 wangeditor 富文本编辑器 demo 实例,无法换行#I5565B](https://gitee.com/lyt-top/vue-next-admin/issues/I5565B),感谢@[jenchih](https://gitee.com/jenchih)
- 🎯 优化 [在关闭 tagview 时,高度刷新时会会变化,出现滚动条](https://gitee.com/lyt-top/vue-next-admin/issues/I55FHM),感谢[张松](https://gitee.com/zs310071113)
- 🎯 优化 [路由参数](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)演示
- 🎉 新增 [vuex](https://vuex.vuejs.org/) 替换成 [pinia](https://pinia.vuejs.org/getting-started.html)
- 🎉 新增 tagsView 支持自定义 tagsView 名称(文章详情时有用),前往体验:[路由参数/普通路由](https://lyt-top.gitee.io/vue-next-admin-preview/#/params/common)。新增 tagsView 支持自定义名称国际化,感谢[@q7but](https://gitee.com/q7but)、[!22 add 添加自定义 tagVIewName 拓展,支持国际化](https://gitee.com/lyt-top/vue-next-admin/pulls/22/files)、感谢[@tony_tong_xin](https://gitee.com/tony_tong_xin)
- 🐞 修复 适配 `"element-plus": "^2.1.9"2.2.0` 版本
- 🐞 修复 [导航栏横向布局后,一级菜单显示问题#I4Z3M3](https://gitee.com/lyt-top/vue-next-admin/issues/I4Z3M3)
- 🐞 修复 横向布局三级及以上导航菜单高亮、导航高度不统一问题
- 🐞 修复 分栏模式下,选中的菜单是 primary 样式,鼠标移入字也变成 primary 色了,感谢群友@孤夜-流殇
- 🐞 修复 [vuex 里面改了颜色 但是不生效 #I4WFMA](https://gitee.com/lyt-top/vue-next-admin/issues/I4WFMA)
- 🐞 修复 全局主题 primary 清空颜色后报错,[#I4X0LG](https://gitee.com/lyt-top/vue-next-admin/issues/I4X0LG),感谢[面向 BUG 编程](https://gitee.com/fhtfy)
- 🐞 修复 [.eslintrc.js 文件 rules 标签名错误 #I53IPK](https://gitee.com/lyt-top/vue-next-admin/issues/I53IPK),感谢[yuyong1566](https://gitee.com/yuyong1566)
- 🐞 修复 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题
- 🐞 修复 `router.push` 路径找不到时报错问题,`404、401 界面` 已移入到 `main` 主布局里(之前全屏)
- 🐞 修复 [全局修改组件大小失效了](https://gitee.com/lyt-top/vue-next-admin/issues/I551RP),感谢[lg_boy](https://gitee.com/lg_boy)
- 🐞 修复 [修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效,问题解决#I567R1](https://gitee.com/lyt-top/vue-next-admin/issues/I567R1),感谢[@lanbao123](https://gitee.com/lanbao123)
- 🐞 修复 [标记为需要缓存的 tab 页后,再次从左侧菜单打开,还是显示被缓存的页面内容#I4UY3G](https://gitee.com/lyt-top/vue-next-admin/issues/I4UY3G),感谢@axcc1234、特别感谢群友@华仔
- 🌈 重构 路由(`/src/router/index.ts`)解决 No match found for location with path "xxx"(前端控制,后端控制未解决) 问题
## 2.0.2
`2022.03.04`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 Alert 提示添加边框
- 🎯 优化 功能 / 数字滚动 演示界面
- 🐞 修复 全局主题按钮颜色 :active 问题
- 🐞 修复 Dropdown 下拉菜单样式问题
- 🐞 修复 SvgIcon 图标组件动态切换时报警告问题,[SvgIcon 改变 name 时可能导致图像不显示](https://gitee.com/lyt-top/vue-next-admin/issues/I4VGE0),感谢@axcc1234
## 2.0.1
`2022.02.25`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 svgIcon 图标组件
- 🎯 优化 vite.config.ts 打包,感谢群友@YourObjec
- 🐞 修复 tagViews 开启图标不显示问题(风格 5感谢群友@坏人
- 🐞 修复 [Element Plus 1.2.0-beta.6 以后的版本 el-table 在移动端无法左右滑动](https://gitee.com/lyt-top/vue-next-admin/issues/I4UPTP),感谢@YGDada
## 2.0.0
`2022.02.21`
⚡⚡⚡ 此版本为破环性更新,优化内容如下:(谨慎更新!谨慎更新!!谨慎更新!!!)。演示界面建议直接覆盖文件。如需使用之前版本,请前往[gitee 发行版](https://gitee.com/lyt-top/vue-next-admin/releases) 进行对应版本下载。基础版会基于 `master` 分支进行修改
- 🌟 更新 依赖更新最新版本
- 🌟 更新 登录页、首页
- 💔 移除 vue-web-screen-shot
- 💔 移除 城市多级联动,完整 json 数据请去 [vue-next-admin-images/menu](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 仓库查看
- 💔 移除 功能/echartsTree 树图
- 💔 移除 其它设置/Tagsview 风格 2、Tagsview 风格 3
- 💔 移除 功能/验证器
- 🚧 调整 src/api 编写方式
- 🚧 调整 自定义封装公用组件演示,更好的维护
- 🎉 新增 Volar 支持vs code 配置参考 [Vue Language Features (Volar)](https://lyt-top.gitee.io/vue-next-admin-doc-preview/home/vscode/)
- 🎉 新增 `SvgIcon` 支持本地 svg 图标使用
- 🎉 新增 表单表格验证演示
- 🎯 优化 全局主题(移除 success、info、warning、danger
- 🎯 优化 工作流(开源)
- 🎯 优化 element plus svg 图标,`elementXXX` 改成 `ele-XXX`
- 🌈 重构 深色模式
- 🌹 合并 [处理 parent 的 h100 由于外层有 min-height 导致失效的问题](https://gitee.com/lyt-top/vue-next-admin/pulls/20),感谢@MaxNull@21030442-mao
- 🐞 修复 element plus 升级 `^1.3.0-beta.5` 后 组件 size 大小问题(大改:涉及布局、演示界面)
- 🐞 修复 vs code 使用 Vue Language Features (Volar) 插件 代码报红问题(可以把公用的 ts 类型定义封装起来公用)
## 1.2.2
`2021.12.21`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 iframes 滚动条问题
- 🎯 优化 部署后每次都要强制刷新清浏览器缓存问题
- 🎉 新增 工具类百分比验证演示
- 🐞 修复 [tag-view 标签右键会超出浏览器 #I4KN78](https://gitee.com/lyt-top/vue-next-admin/issues/I4KN78)
## 1.2.1
`2021.12.12`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 cropper 裁剪时卡顿问题 [#I4M2VQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4M2VQ)
- 🎯 优化 Wangeditor 富文本编辑器的问题 [#I4LPC1](https://gitee.com/lyt-top/vue-next-admin/issues/I4LPC1)、[#I4LM7I](https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I)
- 🐞 修复 浏览器标题问题
- 🐞 修复 element plus svg 图标引入
- 🐞 修复 工作流不可以拖线连接问题
## 1.2.0
`2021.11.28`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 深色模式
- 🎯 优化 `/@/utils` 文件夹,合并删除单一内容
- 🎯 优化 系统设置:菜单管理(新增、修改)、角色管理(新增菜单权限)、用户管理、部门管理、字典管理
- 🎯 优化 登录界面逻辑、权限管理逻辑
- 🎯 优化 同步 [vue-next-admin-images](https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu) 后端控制菜单模拟数据
- 🎉 新增 适配 Font Icon 向 SVG Icon 迁移(改动大,"element-plus": "^1.2.0-beta.4" 谨慎更新)
- 🐞 修复 热更新问题,感谢@甜蜜蜜
- 🐞 修复 页面/element 字体图标演示
- 🐞 修复 功能/图标选择器演示,新增高级功能 [issues #I4GJXQ](https://gitee.com/lyt-top/vue-next-admin/issues/I4GJXQ)
## 1.1.2
`2021.10.17`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 开启全屏时,刷新界面被还原成未全屏的状态
- 🎯 优化 tagsView 右键菜单关闭逻辑
- 🎯 优化 wangeditor 富文本编辑器(增加双向绑定)
- 🎉 新增 工作流(暂不开源)
- 🎉 新增 基础版 ts不带国际化切换 `vue-next-admin-template` 分支
## 1.1.1
`2021.09.25`
- 🌟 更新 依赖更新最新版本(`"element-plus": "^1.1.0-beta.13"` 版本运行错误,`^1.1.0-beta.16`修复横向菜单卡死问题)
- 🐞 修复 Dialog 弹窗位置错误、Drawer 抽屉内边距、el-menu 菜单收起时背景色问题
- 🎯 优化 锁屏界面自动锁屏(s/秒)必须设置至少 1 秒
- 🎉 新增 分栏布局,鼠标移入当前项时,显示当前项菜单内容
- 🎉 新增 工作流(未完成)
## 1.1.0
`2021.09.10`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 小屏模式下登录页二维码遮挡标题问题
- 🎉 新增 图片验证器
- 🎉 新增 动态复杂表单
- 🎉 新增 工作流(未完成)
- 🎉 新增 深色主题(伪深色,样式变动大,谨慎更新)
## 1.0.18
`2021.08.29`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 权限组件去掉顶级 div`/src/components/auth`
- 🎉 新增 布局配置添加恢复默认按钮
- 🐞 修复 升级 <a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">element plus 1.1.0-beta.7</a>后项目无法启动、el-menu 菜单
- 🐞 修复 表格固定列时的层级、设置了相对定位时,遮挡左侧导航菜单问题
## 1.0.17
`2021.08.22`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 去除设置布局切换重置主题样式initSetLayoutChange切换布局需手动设置样式设置的样式自动同步各布局
- 🎯 优化 Dropdown 下拉菜单用户账号靠边时换行问题
- 🎯 优化 左侧导航菜单,共用菜单树,防止 `布局配置` 设置 `菜单 / 顶栏` 时,样式丢失等问题
- 🐞 修复 固定 header 后没有回到顶部的 bug拉取项目后运行不起来的 bug。<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/14" target="_blank">!14</a>,感谢<a href="https://gitee.com/wjs0509" target="_blank">@wjs0509</a>
- 🐞 修复 tagView 右键全屏后,浏览器窗口大小发生任何变化都会导致左边菜单显示出来,并且可点击打开对应页面。<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I46E6T" target="_blank">I46E6T</a>
- 🐞 修复 默认设置 `菜单 / 顶栏` 样式不生效问题(/@/src/store/modules/themeConfig.ts
## 1.0.16
`2021.08.14`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 菜单高亮(详情且详情设置了 meta.isHide 时,顶级菜单高亮),感谢群友@YourObject
- 🎯 优化 详情路径写法:如父级(/pages/filtering那么详情为/pages/filtering/details?id=1。这样写可实现详情时父级菜单高亮否则写成/pages/filteringDetails?id=1顶级菜单将不会高亮。可参考`页面/过滤筛选组件`,点击当前图片进行测试
- 🎯 优化 tagsView 右键菜单全屏时,打开的界面高度问题
- 🎯 优化 图表批量 resize 问题
- 🐞 修复 菜单收起时设置全局主题primary 且有二级菜单时),文字高亮颜色不对
- 🐞 修复 国际化 <a href="https://gitee.com/lyt-top/vue-next-admin/issues/I43NPE" target="_blank">#I43NPE</a>。可参考:`页面/过滤筛选组件`,点击顶部语言切换,进行底部分页国际化查看
## 1.0.15
`2021.08.06`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 tagsView 右键菜单点击时的字段名id 已修改成 contextMenuClickId与路由中返回的 id 名冲突问题,感谢群友@伯牙已遇钟子期
- 🎉 新增 多个 form 表单验证界面演示
## 1.0.14
`2021.07.29`
- 🌟 更新 依赖更新最新版本vue、vuex、vue-router,出现问题,请手动降级。版本查看:<a href="https://www.npmjs.com/" target="_blank">vnpm</a>
- 🎯 优化 数据可视化图表演示加载卡顿问题、优化有图表的演示界面
- 🎯 优化 路由参数演示界面
- 🎯 优化 tagsView 操作演示界面由于存在相同路由多标签必须要传全部参数值query 或者 params
- 🎉 新增 开启 TagsView 共用开启时多个路由菜单共用一个详情组件参数为后点击的覆盖前面点击的tagsView 中只会出现一个(不支持同时出现多个 tagsView 标签))。关闭时:(多个路由菜单共用一个详情组件,参数不同,会同时出现多个 tagsView 标签)
- 🐞 修复 tagsView 共用(单标签)时,右键菜单功能点击,参数不对的问题(第 2n+个参数未覆盖第一个参数值)
- 🐞 修复 多 tagsView 标签(参数不同)、单个 tagsView 标签公用(参数不同)所带来的刷新功能、横向自动滚动等问题
- 🐞 修复 处理全屏若干问题,<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/12" target="_blank">pr!12</a>,感谢群友@另一个前端
## 1.0.13
`2021.07.25`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2
- 🎉 新增 登录页扫码登录
## 1.0.12
`2021.07.16`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示空界面(待完善)
- 🎯 优化 tagsView 动态路由xxx/:id/:name时的右键菜单刷新、关闭其它时参数丢失问题2021.07.15 优化)
- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
## 1.0.11
`2021.07.14`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 路由参数、图片懒加载界面演示
- ⚠️ 警告 Form 表单 `binding value must be a string or number`,解决:加上 `label-position="top"` 不报警告(等待官方修复)
- 🎯 优化 锁屏界面动画效果、首页图表显示
- 🎯 优化 tagsView 右键菜单 `关闭` 功能逻辑
- 🐞 修复 开启 TagsView 拖拽报错及小于 `1000px` 时自动设置禁止拖拽(<a href="https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI" target="_blank">#I3ZRRI</a>
- 🐞 修复 `iframe 内嵌、外链` 高度问题,使用 computed 进行计算
- 🐞 修复 默认布局开启 `侧边栏 Logo` 与关闭 `菜单水平折叠`,切换到横向布局时,菜单看不见的问题
- 🐞 修复 切换不同布局时,再去开启 `经典布局分割菜单` 功能不生效问题
- 🐞 修复 浏览器窗口标题中/英文切换不实时生效的问题
- 🐞 修复 切换布局时,某些功能不可以使用。部分界面不需要取消事件监听(proxy.mittBus.off('xxx'))
- 🐞 修复 动态路由带参数router-link 跳转问题(<a href="hhttps://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G" target="_blank">#I3YX6G</a>
- 🐞 修复 横向菜单有二级菜单时,点击子级菜单不高亮问题
- 🐞 修复 功能 tagsView 操作演示不生效
## 1.0.10
`2021.07.07`
- 🌟 更新 依赖更新最新版本(字体图标无问题)
- 🎯 优化 内嵌 iframe、外链解决 tagsView 刷新问题
## 1.0.9
`2021.07.02`
- 🌟 更新 依赖更新最新版本
- 🎯 优化 图标选择器设置宽度、v-model 等问题
- 🎯 优化 滚动通知栏在手机上的体验
- 🎯 优化 系统管理/新增菜单(编辑菜单),使用 `图标选择器` 进行模拟
- 🎯 优化 字体图标(自动载入) 逻辑
- 🐞 修复 screenfull 全屏时,按键盘 esc 键图标不改变问题,感谢群友@伯牙已遇钟子期
## 1.0.8
`2021.06.29`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 表单中英文切换演示
- 🎯 优化 登录页查看密码 icon 图标
- 🎯 优化 图标选择器
- 🎯 优化 拖动指令
- 🐞 修复 form 表单在页面小于 576px 时的排版问题
## 1.0.7
`2021.06.24`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 拖动指令及其演示界面
- 🎯 优化 锁屏界面,解锁提示
- 🎯 优化 登录页在手机上显示的效果
## 1.0.6
`2021.06.23`
- 🎯 优化 去掉内嵌 iframe 内边距padding
- 🎯 优化 城市多级联动组件
- 🎯 优化 Tree 树形控件改成表格组件
- 🐞 修复 Cascader 级联选择器高度问题
## 1.0.5
`2021.06.22`
- 🌟 更新 vite 降级为@vite2.3.7,降级方法 `cnpm install vite@2.3.7`,防止 element plus 字体图标消失
- 🐞 修复 开启后端控制路由isRequestRoutes = true内嵌 iframe、外链不可使用的问题
## 1.0.4
`2021.06.19`
- 🌟 更新 依赖更新最新版本("vite": "^2.3.7")热更新无问题
- 🎉 新增 深克隆工具,方便开发,感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/6" target="_blank">#6</a>)
- 🎯 优化 vuex 模块自动导入。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/4" target="_blank">#4</a>),感谢群友@web 小学生-第五君
- 🎯 优化 类型定义提高编码体验修复不能将类型“string | undefined”分配给类型“string”的问题。感谢<a href="https://gitee.com/kangert" target="_blank">@kangert</a>(<a href="https://gitee.com/lyt-top/vue-next-admin/pulls/5" target="_blank">#5</a>)
- 🎯 优化 `layout` 文件夹移动到与 `views` 文件夹同级(改动较大,`/@/views/layout` 变成 `/@/layout`
- 🎯 优化 页面有 `console.log``eslint` 不生效问题
- 🎯 优化 页面、ts 中 `any` 类型问题(改动较大)
- 🎯 优化 登录页在手机上显示的效果
- 🎯 优化 多行注释信息,鼠标放到方法名即可查看,更加直观的知道方法参数等。引入方法时需去掉以 `.ts` 结尾的后缀(改动较大)
- 🎯 优化 移除 `utils/storage.ts` 下的旧写法(改动较大)
- 🎯 优化 拆分 `router` 下内容,路由、前端、后端控制分开写,方便理解
- 🐞 修复 鼠标移入顶部用户信息栏 `开/关全屏` 文字反向问题
- 🐞 修复 热更新时NextLoading界面 loading 不消失问题 `window.nextLoading === undefined`
- 🐞 修复 vuex 中不可以使用 `/@/api/xxx` 下的接口调用问题
## 1.0.3
`2021.06.02`
- ❄️ 删除 G6 思维导图界面
- 🌟 更新 手动更新 vue、vue-router、vuex 到最近最多人使用的版本,出现不可预测的问题请降低版本。版本查看:<a href="https://www.npmjs.com/package/vue" target="_blank">vue 版本查看</a>
- 🐞 修复 开启后端控制路由 `isRequestRoutes` 在非首页刷新页面后,回到首页的问题,感谢群友@伯牙已遇钟子期
## 1.0.2
`2021.06.01`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 菜单搜索中文不可以搜索的问题,感谢群友@逍遥天意
## 1.0.1
`2021.05.31`
- 🎉 新增 更新日志文件 `CHANGELOG.md`,以后每次更新都会在这里显示对应内容
- 🌟 更新 依赖更新最新版本
- 🐞 修复 分栏、经典布局路由设置 `meta.isHide``true` 时报错问题,感谢群友@29@芭芭拉
- 🐞 修复 经典布局点击 `tagsView` 左侧菜单数据不变问题

21
LICENSE
View File

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

View File

@ -1,57 +0,0 @@
<div align="center">
<img src="https://yxh-1301841944.cos.ap-chongqing.myqcloud.com/gfast/2022-04-19/gfastlogo.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://www.tslang.cn/" target="_blank">
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
</a>
<a href="https://vitejs.dev/" target="_blank">
<img src="https://img.shields.io/badge/vite-%3E2.0.0-yellow" alt="vite">
</a>
<a href="https://gitee.com/lyt-top/vue-next-admin/blob/master/LICENSE" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
</a>
</p>
<p>&nbsp;</p>
</div>
#### 🌈 介绍
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### 🚧 安装 cnpm、yarn
- 复制代码(桌面 cmd 运行) `npm install -g cnpm --registry=https://registry.npm.taobao.org`
- 复制代码(桌面 cmd 运行) `npm install -g yarn`
#### 🏭 环境支持
| Edge | last 2 versions | last 2 versions | last 2 versions |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| ![Edge](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
#### ⚡ 使用说明
建议使用 cnpm因为 yarn 有时会报错。<a href="http://nodejs.cn/" target="_blank">node 版本[v16.x ~ v20.x)</a>
```bash
# 克隆项目
git clone -b v3.2 https://gitee.com/tiger1103/gfast-ui.git
# 进入项目
cd gfast-ui
# 安装依赖
npm install --registry=https://registry.npmmirror.com
# 运行项目
npm run dev
# 打包发布
npm run build
```
## ❤特别鸣谢
* 感谢[VUE-NEXT-ADMIN](https://github.com/lyt-Top/vue-next-admin)

3551
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,15 @@
"@codemirror/lang-javascript": "^6.1.1", "@codemirror/lang-javascript": "^6.1.1",
"@codemirror/theme-one-dark": "^6.1.0", "@codemirror/theme-one-dark": "^6.1.0",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"axios": "1.7.4", "@vue/reactivity": "^3.5.18",
"axios": "1.8.2",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"countup.js": "^2.8.0", "countup.js": "^2.8.0",
"cropperjs": "^1.6.0", "cropperjs": "^1.6.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"echarts-gl": "^2.0.9", "echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "^2.8.7", "element-plus": "^2.9.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsplumb": "^2.15.6", "jsplumb": "^2.15.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -40,14 +41,16 @@
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue-demi": "^0.14.7", "vue-demi": "^0.14.7",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.10.2", "vue-i18n": "11.1.2",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-simple-uploader": "^1.0.0-beta.5", "vue-simple-uploader": "^1.0.0-beta.5",
"vue-ueditor-wrap": "^3.0.8" "vue-ueditor-wrap": "^3.0.8"
}, },
"devDependencies": { "devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.11.28", "@types/node": "^20.11.28",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qs": "^6.14.0",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.2.0",
@ -60,8 +63,8 @@
"sass": "^1.80.7", "sass": "^1.80.7",
"sass-loader": "^16.0.3", "sass-loader": "^16.0.3",
"typescript": "^5.4.2", "typescript": "^5.4.2",
"vite": "5.4.6", "vite": "6.3.4",
"vite-plugin-cdn-import": "^0.3.5", "vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-setup-extend-plus": "^0.1.0", "vite-plugin-vue-setup-extend-plus": "^0.1.0",
"vue-eslint-parser": "^9.4.1" "vue-eslint-parser": "^9.4.1"

3524
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,4 @@
onlyBuiltDependencies:
- '@parcel/watcher'
- esbuild
- vue-demi

View File

@ -43,10 +43,11 @@ export function deleteType(dictIds:number[]) {
// 获取字典选择框列表 // 获取字典选择框列表
export function optionselect() { export function optionselect(all?:boolean) {
return request({ return request({
url: '/api/v1/system/dict/type/optionSelect', url: '/api/v1/system/dict/type/optionSelect',
method: 'get' method: 'get',
params:{all}
}) })
} }

View File

@ -88,12 +88,12 @@ export function getJobLogs(req:object){
} }
// 删除定时任务日志 // 删除定时任务日志
export function delSysJobLog(logIds:number[]) { export function delSysJobLog(targetName:string) {
return request({ return request({
url: '/api/v1/system/sysJob/deleteLogs', url: '/api/v1/system/sysJob/deleteLogs',
method: 'delete', method: 'delete',
data:{ data:{
logIds:logIds targetName
} }
}) })
} }

View File

@ -0,0 +1,181 @@
<template>
<div>
<el-table
ref="tableRef"
:data="data"
:row-key="rowKey"
:border="border"
:stripe="stripe"
:loading="loading"
:height="tableHeight"
:size="size"
:highlight-current-row="highlightCurrentRow"
@row-dblclick="onRowDblClick"
@selection-change="onSelectionChange"
@sort-change="onSortChange"
v-bind="tableProps"
>
<!-- 展开行 -->
<el-table-column v-if="showExpand" type="expand" align="center" :width="expandWidth">
<template #default="scope">
<slot name="expand" v-bind="scope" />
</template>
</el-table-column>
<!-- 选择框 -->
<el-table-column v-if="showSelection" type="selection" width="55" align="center" />
<!-- 序号 -->
<el-table-column v-if="showIndex" type="index" width="55" align="center" label="#" />
<template v-for="col in columns" :key="col.prop">
<el-table-column
v-if="col.isFormater"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:sortable="col.sortable || false"
:fixed="col.fixed || false"
:show-overflow-tooltip="col.showOverflowTooltip || true"
:formatter="col.formater"
/>
<el-table-column
v-else
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:align="col.align || 'center'"
:sortable="col.sortable || false"
:fixed="col.fixed || false"
:show-overflow-tooltip="col.showOverflowTooltip || true"
>
<template #default="scope">
<slot v-if="$slots[col.prop]" :name="col.prop" v-bind="scope" />
<component
v-else-if="col.render"
:is="col.render"
v-bind="{ row: scope.row, col, index: scope.$index }"
/>
<span v-else>{{ scope.row[col.prop] }}</span>
</template>
</el-table-column>
</template>
<!-- 操作列 -->
<el-table-column
v-if="$slots.actions"
label="操作"
:fixed="actionFixed"
:width="actionWidth"
align="center"
class-name="small-padding"
>
<template #default="scope">
<slot name="actions" v-bind="scope" />
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { type TableColumnCtx } from 'element-plus';
import { TableColumn } from '../type';
interface Props {
columns: TableColumn[];
data: Record<string, any>[];
loading?: boolean;
rowKey?: string;
border?: boolean;
stripe?: boolean;
size?: 'small' | 'default' | 'large';
showIndex?: boolean;
showSelection?: boolean;
showExpand?: boolean; //
expandWidth?: string | number; //
highlightCurrentRow?: boolean;
actionWidth?: string | number;
actionFixed?: 'left' | 'right' | boolean;
tableProps?: Record<string, any>; // Element Plus Table props
autoHeight?: boolean;
heightOffset?: number; //
}
const props = withDefaults(defineProps<Props>(), {
columns: () => [],
data: () => [],
loading: false,
rowKey: 'id',
border: false,
stripe: true,
size: 'default',
showIndex: false,
showSelection: false,
showExpand: false, //
expandWidth: 55, //
highlightCurrentRow: false,
actionWidth: 200,
actionFixed: 'right',
tableProps: () => ({}),
autoHeight: true,
heightOffset: 360,
});
defineOptions({ name: 'ProTable' });
const emit = defineEmits<{
(e: 'row-dblclick', row: any): void;
(e: 'selection-change', selection: any[]): void;
(e: 'sort-change', col: TableColumnCtx<any>): void;
}>();
//
const tableHeight = ref<string | number>('auto');
const calcHeight = () => {
if (props.autoHeight) {
tableHeight.value = window.innerHeight - props.heightOffset;
}
};
//
onMounted(() => {
calcHeight();
window.addEventListener('resize', calcHeight);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', calcHeight);
});
//
const onRowDblClick = (row: any) => emit('row-dblclick', row);
const onSelectionChange = (selection: any[]) => emit('selection-change', selection);
const onSortChange = (col: TableColumnCtx<any>) => emit('sort-change', col);
// Element Plus Table API
const tableRef = ref();
const clearSelection = () => tableRef.value?.clearSelection();
const toggleRowSelection = (row: any, selected?: boolean) =>
tableRef.value?.toggleRowSelection(row, selected);
const getSelectionRows = () => tableRef.value?.getSelectionRows();
//
const toggleRowExpansion = (row: any, expanded?: boolean) =>
tableRef.value?.toggleRowExpansion(row, expanded);
const setExpandedRows = (rows: any[]) => tableRef.value?.setExpandedRows(rows);
defineExpose({
clearSelection,
toggleRowSelection,
getSelectionRows,
tableRef,
toggleRowExpansion, //
setExpandedRows, //
});
</script>

View File

@ -0,0 +1,114 @@
<template>
<el-dialog :model-value="visible" :title="title" width="30%" @close="handleCancel">
<el-form :model="form" ref="formRef" label-width="80px" :rules="rules">
<slot>
<el-form-item label="审批意见" prop="auditView">
<el-input v-model="form.auditView" placeholder="请输入审批意见" />
</el-form-item>
<el-form-item label="审核状态" prop="auditStatus">
<el-radio v-model="form.auditStatus" label="1" size="large">通过</el-radio>
<el-radio v-model="form.auditStatus" label="2" size="large">不通过</el-radio>
</el-form-item>
<el-form-item label="审核人" prop="auditUser">
<el-select v-model="form.auditUser" placeholder="请选择审核人" filterable clearable>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.userNickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="日期" prop="auditDate">
<el-date-picker
v-model="form.auditDate"
type="date"
placeholder="请选择日期"
style="width: 100%"
/>
</el-form-item>
</slot>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch, defineProps, defineEmits } from 'vue';
import type { AuditFormModel } from '../type';
import { UserItem, VersionMap } from '/@/types';
defineOptions({
name: 'AuditForm',
});
const props = defineProps<{
modelValue: boolean;
title?: string;
defaultForm?: Partial<AuditFormModel>;
}>();
const formRef = ref();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'submit', data: any): void;
}>();
const rules = ref({
auditView: [{ required: true, message: '请输入审批意见', trigger: 'blur' }],
auditStatus: [{ required: true, message: '请选择审核状态', trigger: 'change' }],
auditUser: [{ required: true, message: '请选择审核人', trigger: 'change' }],
auditDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
});
const visible = ref(props.modelValue);
const userList = ref<UserItem[]>([]);
watch(
() => props.modelValue,
(val) => (visible.value = val)
);
watch(visible, (val) => emit('update:modelValue', val));
const form = ref<AuditFormModel>({
id: '',
auditView: '',
auditStatus: '',
auditUser: '',
auditDate: '',
...(props.defaultForm || {}),
});
const open = (row: AuditFormModel, userOptions: UserItem[]) => {
visible.value = true;
row.auditStatus='1'
form.value = row;
userList.value = userOptions;
};
defineExpose({
open,
});
const handleSubmit = () => {
formRef.value.validate((valid: boolean) => {
if (valid) {
emit('submit', form.value);
handleCancel();
} else {
console.log('error submit!!');
return false;
}
});
};
const handleCancel = () => {
visible.value = false;
form.value = {
id: '',
auditView: '',
auditStatus: '',
auditUser: '',
auditDate: '',
};
};
</script>

View File

@ -0,0 +1,176 @@
<template>
<el-dialog
v-model="localVisible"
:title="title"
:width="width"
:close-on-click-modal="false"
@closed="handleCancel"
@open="handleOpen"
v-bind="$attrs"
>
<el-form
ref="formRef"
:model="modelValue"
:rules="rules"
:label-width="labelWidth"
:label-position="labelPosition"
>
<el-row :gutter="gutter">
<template v-for="(item, index) in fields" :key="item.prop">
<el-col :span="item.span||24">
<el-form-item :label="item.label" :prop="item.prop" :rules="item.rules">
<!-- 插槽优先 -->
<slot :name="item.prop" :item="item" :form="modelValue">
<component
:is="getComponent(item.type)"
v-model="modelValue[item.prop]"
v-bind="getProps(item)"
>
<!-- 下拉选项 -->
<template v-if="item.type === 'select'" #default>
<el-option
v-for="opt in item.options || []"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</template>
</component>
</slot>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 操作按钮 -->
<template #footer v-if="showFooter">
<slot name="footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</slot>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
import {PopupFormField} from '../type'
defineOptions({ name: 'ModalForm' });
const props = defineProps<{
visible: boolean;
title?: string;
width?: string;
modelValue: Record<string, any>;
fields: PopupFormField[];
labelWidth?: string;
labelPosition?: 'left' | 'right' | 'top';
gutter?: number;
showFooter?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:modelValue', val: Record<string, any>): void;
(e: 'submit', val:any): void;
(e: 'cancel'): void;
(e: 'open'): void;
}>();
const formRef = ref<FormInstance>();
const localVisible = ref(props.visible);
watch(() => props.visible, val => localVisible.value = val);
watch(localVisible, val => emit('update:visible', val));
const labelWidth = computed(() => props.labelWidth || '100px');
const labelPosition = computed(() => props.labelPosition || 'right');
const gutter = computed(() => props.gutter || 10);
const showFooter = computed(() => props.showFooter !== false);
const rules = computed<FormRules>(() => {
const map: FormRules = {};
props.fields.forEach(field => {
if (field.rules) map[field.prop] = field.rules;
});
return map;
});
const componentMap: Record<string, any> = {
input: 'el-input',
select: 'el-select',
date: 'el-date-picker',
dateTime: 'el-date-picker',
daterange: 'el-date-picker',
datetimerange: 'el-date-picker',
switch: 'el-switch',
radio: 'el-radio-group',
checkbox: 'el-checkbox-group',
upload: 'el-upload',
cascader: 'el-cascader',
rate: 'el-rate',
slider: 'el-slider',
timePicker: 'el-time-picker',
inputNumber: 'el-input-number',
colorPicker: 'el-color-picker',
treeSelect: 'el-tree-select',
};
const getComponent = (type?: string) => componentMap[type || 'input'] || 'el-input';
const getProps = (item: PopupFormField) => {
const props = item.componentProps || {};
if (item.type === 'select') {
return { ...props, filterable: true };
}
if (item.type === 'daterange' || item.type === 'datetimerange') {
return {
...props,
type: item.type,
'value-format': 'YYYY-MM-DD',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
};
}
return props;
};
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate((valid) => {
if (valid) emit('submit', props.modelValue);
});
};
const handleCancel = () => {
emit('cancel');
localVisible.value = false;
formRef.value?.resetFields();
};
const handleOpen = () => {
emit('open');
};
const loadOptions = async () => {
const loaded = new Set();
for (const field of props.fields) {
if (field.fetchOptions && !loaded.has(field.fetchOptions)) {
field.options = await field.fetchOptions();
loaded.add(field.fetchOptions);
}
}
};
onMounted(loadOptions);
</script>
<style scoped>
.form-item-wrapper {
display: block;
}
</style>

View File

@ -0,0 +1,199 @@
<template>
<div class="query-form">
<el-form ref="formRef" :model="modelValue" :label-width="labelWidth" :inline="inline">
<el-row :gutter="gutter">
<template v-for="(item, index) in fields" :key="item.prop">
<div class="form-item-wrapper" v-show="showAll || index < defaultFieldCount">
<el-form-item :label="item.label" :prop="item.prop" :rules="item.rules">
<!-- 插槽优先 -->
<slot :name="item.prop" :item="item" :form="modelValue">
<!-- 动态组件渲染 -->
<component
:is="getComponent(item.type)"
v-model="modelValue[item.prop]"
v-bind="getProps(item)"
@keyup.enter="item.type === 'input' && handleSearch()"
>
<!-- 下拉选项 -->
<template v-if="item.type === 'select'" #default>
<el-option
v-for="opt in item.options || []"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</template>
</component>
</slot>
</el-form-item>
</div>
</template>
<!-- 操作按钮 -->
<div class="form-item-wrapper actions">
<el-form-item>
<slot name="actions">
<el-button type="primary" :icon="Search" size="default" @click="handleSearch">
搜索
</el-button>
<el-button :icon="Refresh" size="default" @click="handleReset">重置</el-button>
<el-button link v-if="fields.length > defaultFieldCount" @click="toggleSearch">
{{ word }}
<el-icon>
<ele-ArrowUp v-if="showAll" />
<ele-ArrowDown v-else />
</el-icon>
</el-button>
</slot>
</el-form-item>
</div>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { Search, Refresh } from '@element-plus/icons-vue';
import type { FormInstance } from 'element-plus';
import { QueryFormField } from '../type';
interface Props {
fields: QueryFormField[];
modelValue: Record<string, any>; //
defaultFieldCount?: number;
labelWidth?: string;
inline?: boolean;
gutter?: number;
}
const props = withDefaults(defineProps<Props>(), {
fields: () => [] as QueryFormField[],
modelValue: () => ({}),
defaultFieldCount: 3,
labelWidth: '100px',
inline: true,
gutter: 20,
});
defineOptions({ name: 'QueryForm' });
const emit = defineEmits<{
(e: 'update:modelValue', val: Record<string, any>): void;
(e: 'search', val: Record<string, any>): void;
(e: 'reset'): void;
}>();
const formRef = ref<FormInstance>();
const showAll = ref(false);
const toggleSearch = () => (showAll.value = !showAll.value);
const word = computed(() => (showAll.value ? '收起搜索' : '展开搜索'));
const dates = ['daterange', 'datetimerange'];
/**
* 初始化表单
*/
const initForm = () => {
const form: Record<string, any> = {};
props.fields.forEach((item) => {
form[item.prop] = item.defaultValue ?? '';
});
return form;
};
/**
* 更新父组件的值
*/
const updateModel = (val: Record<string, any>) => {
emit('update:modelValue', val);
};
/**
* 动态组件映射
*/
const componentMap: Record<string, any> = {
input: 'el-input',
select: 'el-select',
daterange: 'el-date-picker',
datetimerange: 'el-date-picker',
};
const getComponent = (type: string) => componentMap[type] || 'el-input';
/**
* 获取组件属性
*/
const getProps = (item: QueryFormField) => {
const commonProps = {
size: item.size || 'default',
placeholder: item.placeholder || '请输入',
};
if (item.type === 'select') {
return { ...commonProps, filterable: true };
}
if (dates.includes(item.type)) {
return {
...commonProps,
type: item.type,
'value-format': item.valueFormat || 'YYYY-MM-DD',
'range-separator': '至',
'start-placeholder': item.staerPlaceholder || '开始日期',
'end-placeholder': item.endPlaceholder || '结束日期',
style: { width: '220px' },
};
}
return commonProps;
};
const loadOptions = async () => {
let oldFetchOptions:any = [];
for (const field of props.fields) {
if (field.fetchOptions) {
if (!oldFetchOptions.includes(field.fetchOptions)) {
field.options = await field.fetchOptions();
oldFetchOptions.push(field.fetchOptions);
}
}
}
};
onMounted(() => {
loadOptions();
});
/**
* 搜索事件防抖
*/
const handleSearch = () => emit('search', props.modelValue);
/**
* 重置事件
*/
const handleReset = () => {
const newForm = initForm();
updateModel(newForm);
emit('reset');
};
/**
* 监听字段变化保持双向绑定
*/
watch(
() => props.modelValue,
(val) => updateModel(val),
{ deep: true }
);
</script>
<style scoped>
.form-item-wrapper {
display: block;
}
.query-form {
padding-bottom: 10px;
}
.actions {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,51 @@
export interface OptionItem {
label: string;
value: string | number;
}
export interface QueryFormField {
label: string; // 显示的 label
prop: string; // 绑定的字段
type: 'input' | 'select' | 'date' | 'daterange' | 'datetimerange' | 'monthrange';
placeholder?: string;
size?: 'small' | 'default' | 'large';
defaultValue?: any;
options?: OptionItem[];
fetchOptions?: () => Promise<OptionItem[]>; // 异步加载 options
valueFormat?: string;
rules?: any[]; // 校验规则
staerPlaceholder?: string;
endPlaceholder?: string;
}
export interface TableColumn {
label: string;
prop: string;
width?: string | number;
minWidth?: string | number;
align?: 'left' | 'center' | 'right';
sortable?: boolean;
fixed?: 'left' | 'right' | boolean;
render?: any;
showOverflowTooltip?: boolean;
formater?: (row: any, column: any, cellValue: any, index: number) => any;
isFormater?: boolean;
}
export interface PopupFormField {
label: string;
prop: string;
type?: string;
rules?: any;
span?: number;
defaultValue?: any;
componentProps?: Record<string, any>;
options?: Array<{ value: any; label: any }>;
fetchOptions?: () => Promise<Array<{ label: string; value: any }>>;
}
export interface AuditFormModel {
auditView: string;
auditStatus: string;
auditUser: string | number;
auditDate: string;
id: string | number;
}

View File

@ -1,100 +1,102 @@
<template> <template>
<div :class="{'hidden':hidden}" class="pagination-container"> <div :class="{ hidden: hidden }" class="pagination-container">
<el-pagination <el-pagination
:background="background" :background="background"
v-model:current-page="currentPage" v-model:current-page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
:layout="layout" :layout="layout"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:pager-count="pagerCount" :pager-count="pagerCount"
:total="total" :total="total"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { toRefs, defineComponent,computed } from 'vue'; import { toRefs, defineComponent, computed } from 'vue';
const props = { const props = {
total: { total: {
required: true, required: true,
type: Number type: Number,
}, },
page: { page: {
type: Number, type: Number,
default: 1 default: 1,
}, },
limit: { limit: {
type: Number, type: Number,
default: 20 default: 20,
}, },
pageSizes: { pageSizes: {
type: Array, type: Array,
default() { default() {
return [10, 20, 30, 50] return [10, 20, 30, 50];
} },
}, },
// 5 // 5
pagerCount: { pagerCount: {
type: Number, type: Number,
default: document.body.clientWidth < 992 ? 5 : 7 default: document.body.clientWidth < 992 ? 5 : 7,
}, },
layout: { layout: {
type: String, type: String,
default: 'total, sizes, prev, pager, next, jumper' default: 'total, sizes, prev, pager, next, jumper',
}, },
background: { background: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
hidden: { hidden: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}; };
export default defineComponent({ export default defineComponent({
name: 'pagination', name: 'pagination',
props: props, props: props,
setup(props,{emit}){ setup(props, { emit }) {
const { page,limit,pageSizes } = toRefs(props); const { page, limit, pageSizes } = toRefs(props);
const currentPage = computed({ const currentPage = computed({
get() { get() {
return page.value; return page.value;
}, },
set(val) { set(val) {
emit('update:page', val) emit('update:page', val);
} },
}); });
const pageSize = computed({ const pageSize = computed({
get() { get() {
return limit.value return limit.value;
}, },
set(val) { set(val) {
emit('update:limit', val) emit('update:limit', val);
} },
}); });
const handleSizeChange = (val:number) => { const handleSizeChange = (val: number) => {
emit('pagination', { page: currentPage.value, limit: val }) emit('pagination', { page: currentPage.value, limit: val });
}; };
const handleCurrentChange=(val:number) => { const handleCurrentChange = (val: number) => {
emit('pagination', { page: val, limit: pageSizes.value }) emit('pagination', { page: val, limit: pageSizes.value });
} };
return { return {
currentPage, currentPage,
pageSize, pageSize,
handleSizeChange, handleSizeChange,
handleCurrentChange handleCurrentChange,
} };
} },
}); });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.pagination-container { .pagination-container {
padding: 32px 16px; padding: 20px 16px;
display: flex;
justify-content: flex-end;
} }
.pagination-container.hidden { .pagination-container.hidden {
display: none; display: none;
} }
</style> </style>

View File

@ -24,18 +24,18 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick"> <!-- <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon :title="$t('message.user.title2')"> <el-icon :title="$t('message.user.title2')">
<ele-Search /> <ele-Search />
</el-icon> </el-icon>
</div> </div> -->
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick"> <!-- <div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i> <i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div> </div> -->
<div class="layout-navbars-breadcrumb-user-icon" @click="removeCacheClick"> <div class="layout-navbars-breadcrumb-user-icon" @click="removeCacheClick">
<i class="fa-trash fa" title="清除缓存"></i> <i class="fa-trash fa" title="清除缓存"></i>
</div> </div>
<div class="layout-navbars-breadcrumb-user-icon"> <!-- <div class="layout-navbars-breadcrumb-user-icon">
<el-popover ref="newPopoverRef" placement="bottom" trigger="click" transition="el-zoom-in-top" :width="500" :persistent="false"> <el-popover ref="newPopoverRef" placement="bottom" trigger="click" transition="el-zoom-in-top" :width="500" :persistent="false">
<template #reference> <template #reference>
<el-badge :is-dot="true"> <el-badge :is-dot="true">
@ -48,7 +48,7 @@
<UserNews @hideNews="hideNews" /> <UserNews @hideNews="hideNews" />
</template> </template>
</el-popover> </el-popover>
</div> </div> -->
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick"> <div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i <i
class="iconfont" class="iconfont"
@ -67,10 +67,10 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item> <el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item> <!-- <el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item> -->
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item> <el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item> <!-- <el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item> -->
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item> <!-- <el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item> -->
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item> <el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -80,7 +80,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, getCurrentInstance, computed, reactive, toRefs, onMounted, defineComponent, watch} from 'vue'; import {ref, getCurrentInstance, computed, reactive, toRefs, onMounted, watch} from 'vue';
import { useRoute,useRouter } from 'vue-router'; import { useRoute,useRouter } from 'vue-router';
import {ElMessageBox, ElMessage, ElNotification} from 'element-plus'; import {ElMessageBox, ElMessage, ElNotification} from 'element-plus';
import screenfull from 'screenfull'; import screenfull from 'screenfull';
@ -90,7 +90,6 @@ import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other'; import other from '/@/utils/other';
import { Session, Local } from '/@/utils/storage'; import { Session, Local } from '/@/utils/storage';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue'; import Search from '/@/layout/navBars/breadcrumb/search.vue';
import {logout} from "/@/api/login"; import {logout} from "/@/api/login";
import {removeCache} from "/@/api/system/cache"; import {removeCache} from "/@/api/system/cache";
@ -197,7 +196,6 @@ const onSearchClick = () => {
searchRef.value.openSearch(); searchRef.value.openSearch();
}; };
const hideNews=()=>{ const hideNews=()=>{
debugger
newPopoverRef.value.hide() newPopoverRef.value.hide()
} }
// //
@ -263,7 +261,7 @@ const noticeStoreAct = noticeStore()
const getMessages = computed(() => { const getMessages = computed(() => {
return noticeStoreAct.message; return noticeStoreAct.message;
}); });
watch(getMessages,(nv,ov)=>{ watch(getMessages,(nv)=>{
if (!nv || !nv.id) { if (!nv || !nv.id) {
return; return;
} }

View File

@ -11,9 +11,7 @@ import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'; import 'element-plus/dist/index.css';
import '/@/theme/index.scss'; import '/@/theme/index.scss';
import mitt from 'mitt'; import mitt from 'mitt';
import VueGridLayout from 'vue-grid-layout';
import {findChildrenByPid, flattenTree, getUpFileUrl, handleTree, parseTime, selectDictLabel} from '/@/utils/gfast'; import {findChildrenByPid, flattenTree, getUpFileUrl, handleTree, parseTime, selectDictLabel} from '/@/utils/gfast';
import Websocket from '/@/utils/websocket';
import {useDict} from '/@/api/system/dict/data'; import {useDict} from '/@/api/system/dict/data';
import {getItems, setItems, getOptionValue, isEmpty} from '/@/api/items' import {getItems, setItems, getOptionValue, isEmpty} from '/@/api/items'
// 分页组件 // 分页组件
@ -27,17 +25,6 @@ import VueUeditorWrap from 'vue-ueditor-wrap';
const app = createApp(App); const app = createApp(App);
// 全局websocket
const onMessageList: Array<Function> = [];
app.provide('onMessageList', onMessageList);
const onMessage = (event: any) => {
onMessageList.forEach((f) => {
f.call(null, event);
});
};
Websocket(onMessage);
directive(app); directive(app);
other.elSvg(app); other.elSvg(app);
@ -47,7 +34,6 @@ app.use(pinia)
.use(router) .use(router)
.use(ElementPlus) .use(ElementPlus)
.use(i18n) .use(i18n)
.use(VueGridLayout)
.use(VueUeditorWrap) .use(VueUeditorWrap)
.mount('#app'); .mount('#app');

View File

@ -1,10 +1,17 @@
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
// Extend the Window interface to include nextLoading
declare global {
interface Window {
nextLoading?: any;
}
}
import pinia from '/@/stores/index'; import pinia from '/@/stores/index';
import { useUserInfo } from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
import { useRequestOldRoutes } from '/@/stores/requestOldRoutes'; import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading'; import { NextLoading } from '/@/utils/loading';
import { demoRoutes,dynamicRoutes, notFoundAndNoPower } from '/@/router/route'; import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index'; import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
import { useRoutesList } from '/@/stores/routesList'; import { useRoutesList } from '/@/stores/routesList';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
@ -47,7 +54,7 @@ export async function initBackEndControlRoutes() {
// 存储接口原始路由未处理component根据需求选择使用 // 存储接口原始路由未处理component根据需求选择使用
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(menuRoute))); useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(menuRoute)));
// 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由 // 处理路由component替换 dynamicRoutes/@/router/route第一个顶级 children 的路由
dynamicRoutes[0].children?.push(...await backEndComponent(menuRoute),...demoRoutes); dynamicRoutes[0].children?.push(...await backEndComponent(menuRoute));
// 添加动态路由 // 添加动态路由
await setAddRoute(); await setAddRoute();
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 // 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组

File diff suppressed because it is too large Load Diff

48
src/types/index.ts Normal file
View File

@ -0,0 +1,48 @@
export interface DeptItem {
deptId: number;
parentId: number;
ancestors: string;
deptName: string;
orderNum: number;
leader: null;
phone: string;
email: string;
status: number;
createdBy: number;
updatedBy: number;
createdAt: string;
updatedAt: string;
deletedAt: null;
};
export interface UserItem {
id: number;
userName: string;
mobile: string;
userNickname: string;
birthday: number;
userPassword: string;
userSalt: string;
userStatus: number;
userEmail: string;
sex: number;
avatar: string;
deptId: number;
remark: string;
isAdmin: number;
address: string;
describe: string;
lastLoginIp: string;
lastLoginTime: string;
createdAt: string;
updatedAt: string;
deletedAt: null;
openId : string;
dept: DeptItem;
roleInfo: null;
post: null;
};
export interface VersionMap{
[key: string]: number
}

View File

@ -30,54 +30,57 @@ export function getUpFileUrl(url:string){
* @param {*} rootId Id 0 * @param {*} rootId Id 0
*/ */
export function handleTree(data:any[], id:string, parentId:string, children:string, rootId:any):any[] { export function handleTree(data:any[], id:string, parentId:string, children:string, rootId:any):any[] {
id = id || 'id' id = id || 'id'
parentId = parentId || 'parentId' parentId = parentId || 'parentId'
children = children || 'children' children = children || 'children'
let rootIds:any = [] let rootIds:any = []
if(typeof rootId === 'boolean' && rootId){ if(typeof rootId === 'boolean' && rootId){
//自动获取rootId //自动获取rootId
let idSet:any = {} let idSet:any = {}
data.map((item:any)=>{ data.map((item:any)=>{
idSet[item[id]] = true idSet[item[id]] = true
}) })
data.map((item:any)=>{ data.map((item:any)=>{
if(!idSet[item[parentId]]){ if(!idSet[item[parentId]]){
rootIds.push(item[parentId]) rootIds.push(item[parentId])
} }
}) })
}else{ }else if(typeof rootId ==='string'){
rootId = rootId || 0 rootId = rootId || ''
rootIds = [rootId] rootIds = [rootId]
} }else{
rootIds = [...new Set(rootIds)] rootId = rootId || 0
let treeData:any = [] rootIds = [rootId]
//对源数据深度克隆 }
const cloneData = JSON.parse(JSON.stringify(data)) rootIds = [...new Set(rootIds)]
rootIds.map((rItem:any)=>{ let treeData:any = []
//循环所有项 //对源数据深度克隆
const td = cloneData.filter((father:any) => { const cloneData = JSON.parse(JSON.stringify(data))
let branchArr = cloneData.filter((child:any) => { rootIds.map((rItem:any)=>{
//返回每一项的子级数组 //循环所有项
return father[id] === child[parentId] const td = cloneData.filter((father:any) => {
}); let branchArr = cloneData.filter((child:any) => {
branchArr.length > 0 ? father[children] = branchArr : ''; //返回每一项的子级数组
//返回第一层 return father[id] === child[parentId]
switch (typeof father[parentId]){ });
case 'string': branchArr.length > 0 ? father[children] = branchArr : '';
if(father[parentId]===''&&rItem===0){ //返回第一层
return true switch (typeof father[parentId]){
} case 'string':
return father[parentId]===rItem.toString(); if(father[parentId]===''&&rItem===0){
case 'number': return true
return father[parentId] === rItem; }
} return father[parentId]===rItem.toString();
return false; case 'number':
}); return father[parentId] === rItem;
if(td.length>0){ }
treeData = [...treeData,...td] return false;
} });
}) if(td.length>0){
return treeData != '' ? treeData : data; treeData = [...treeData,...td]
}
})
return treeData != '' ? treeData : data;
} }
export function flattenTree(treeArray:any[]):any[] { export function flattenTree(treeArray:any[]):any[] {

0
src/utils/index.ts Normal file
View File

View File

@ -0,0 +1,55 @@
import request from '/@/utils/request'
// 查询识别特征列表
export function listCharacteristic(query:object) {
return request({
url: '/api/v1/businesses/characteristic/list',
method: 'get',
params: query
})
}
// 查询识别特征详细
export function getCharacteristic(id:number) {
return request({
url: '/api/v1/businesses/characteristic/get',
method: 'get',
params: {
id: id.toString()
}
})
}
// 新增识别特征
export function addCharacteristic(data:object) {
return request({
url: '/api/v1/businesses/characteristic/add',
method: 'post',
data: data
})
}
// 修改识别特征
export function updateCharacteristic(data:object) {
return request({
url: '/api/v1/businesses/characteristic/edit',
method: 'put',
data: data
})
}
// 删除识别特征
export function delCharacteristic(ids:number[], version: number[]) {
return request({
url: '/api/v1/businesses/characteristic/delete',
method: 'delete',
data:{
ids:ids,
version: version,
}
})
}
export function auditCharacteristic(data: object) {
return request({
url: '/api/v1/businesses/characteristic/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,512 @@
<template>
<div class="businesses-characteristic-container">
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
:labelWidth="'120px'"
ref="queryFormRef"
/>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
v-auth="'api/v1/businesses/characteristic/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/characteristic/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/characteristic/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/characteristic/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/characteristic/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import {
listCharacteristic,
delCharacteristic,
addCharacteristic,
updateCharacteristic,
auditCharacteristic,
} from './api';
import {
CharacteristicTableColumns,
CharacteristicInfoData,
CharacteristicTableDataState,
} from './type';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
import { listSpeciesName } from '../speciesName/api';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
import { SpeciesNameInfoData } from '../speciesName/type';
defineOptions({ name: 'BusinessesCharacteristicList' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<CharacteristicTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '形态学特征', prop: 'formCharacteristic', minWidth: 200 },
{ label: '分子生物学特征', prop: 'biologyCharacteristic', minWidth: 200 },
{ label: '数据来源', prop: 'sourcesData', minWidth: 150 },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
minWidth: 120,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
minWidth: 120,
isFormater: true,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
{ label: '核查意见', prop: 'auditView' },
{ label: '备注', prop: 'remark' },
];
const onSearch = async (val: Record<string, any>) => {
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v] || val[v] === 0) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
getList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
getList();
};
const searchForm = ref<Partial<CharacteristicInfoData>>({
speciesCode: undefined,
formCharacteristic: undefined,
biologyCharacteristic: undefined,
sourcesData: undefined,
createUser: undefined,
auditUser: undefined,
auditStatus: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{ label: '形态学特征', prop: 'formCharacteristic', type: 'input', placeholder: '请输入形态学特征' },
{ label: '分子生物学特征', prop: 'biologyCharacteristic', type: 'input', placeholder: '请输入分子生物学特征' },
{ label: '数据来源', prop: 'sourcesData', type: 'input', placeholder: '请输入数据来源' },
{
label: '核查状态',
prop: 'auditStatus',
type: 'select',
options: [
{ label: '待审核', value: 0 },
{ label: '通过', value: 1 },
{ label: '不通过', value: 2 },
],
placeholder: '请输入物种编码',
},
{
label: '数据采集人',
prop: 'createUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据采集人',
},
{
label: '数据核查人',
prop: 'auditUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据核查人',
},
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
getList();
reqGetUserList();
reqListSpeciesName();
};
//
const getList = async () => {
loading.value = true;
const res = await listCharacteristic(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const specieslist = ref<SpeciesNameInfoData[]>([]);
/**获取物种列表 */
const reqListSpeciesName = async () => {
if (userOptions.value.length > 0) return;
const res = await listSpeciesName({ pageSize: 9999, pageNum: 1 });
specieslist.value = res.data.list;
};
/**获取用户列表 */
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<CharacteristicInfoData>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'select',
options: specieslist.value.map((i) => {
return {
value: i.speciesCode,
label: i.speciesCode,
};
}),
span: 24,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请选择物种编码',
},
},
{
label: '形态学特征',
prop: 'formCharacteristic',
rules: [{ required: true, message: '请输入形态学特征', trigger: 'blur' }],
type: 'input',
span: 24,
componentProps: {
type: 'textarea',
placeholder: '请输入形态学特征',
},
},
{
label: '分子生物学特征',
prop: 'biologyCharacteristic',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入分子生物学特征', trigger: 'blur' }],
componentProps: {
type: 'textarea',
placeholder: '请输入分子生物学特征',
},
},
{
label: '数据来源',
prop: 'sourcesData',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入数据来源', trigger: 'blur' }],
componentProps: {
type: 'textarea',
placeholder: '请输入数据来源',
},
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
rules: [{ required: true, message: '请选择数据采集人', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<Partial<CharacteristicInfoData>>({
speciesCode: undefined,
formCharacteristic: undefined,
biologyCharacteristic: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
remark: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: CharacteristicInfoData) => {
if (formType.value === 'add') {
addCharacteristic(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
getList();
});
} else {
form.auditStatus = 0;
updateCharacteristic(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
getList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
speciesCode: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增分类地位';
formType.value = 'add';
};
const handleUpdate = (row: CharacteristicInfoData) => {
showDialog.value = true;
formTitle.value = '编辑分类地位';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: CharacteristicTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delCharacteristic(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
getList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: CharacteristicTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: CharacteristicTableColumns) => {
auditCharacteristic(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
getList();
});
};
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2 {
margin: 3px;
}
</style>

View File

@ -0,0 +1,54 @@
export interface CharacteristicTableColumns {
id:number; //
speciesCode:string; // 物种编码
formCharacteristic:string; // 形态学特征
biologyCharacteristic:string; // 分子生物学特征
sourcesData:string; // 数据来源
createUser:number; // 数据采集人
createDate:string; // 数据采集日期
auditUser:number; // 数据核查人
auditDate:string; // 数据核查日期
auditStatus:number; //
auditView:string; //
remark:string; //
version:number; //
}
export interface CharacteristicInfoData {
id:number|undefined; //
speciesCode:string|undefined; // 物种编码
formCharacteristic:string|undefined; // 形态学特征
biologyCharacteristic:string|undefined; // 分子生物学特征
sourcesData:string|undefined; // 数据来源
createUser:number|undefined; // 数据采集人
createDate:string|undefined; // 数据采集日期
auditUser:number|undefined; // 数据核查人
auditDate:string|undefined; // 数据核查日期
auditStatus:number|undefined; //
auditView:string|undefined; //
remark:string|undefined; //
version:number|undefined; //
}
export interface CharacteristicTableDataState {
ids:any[];
tableData: {
data: Array<CharacteristicTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface CharacteristicEditState{
loading:boolean;
isShowDialog: boolean;
formData:CharacteristicInfoData;
rules: object;
}

View File

@ -0,0 +1,54 @@
import request from '/@/utils/request'
// 查询分类地位列表
export function listClassifyStatuse(query:object) {
return request({
url: '/api/v1/businesses/classifyStatuse/list',
method: 'get',
params: query
})
}
// 查询分类地位详细
export function getClassifyStatuse(id:number) {
return request({
url: '/api/v1/businesses/classifyStatuse/get',
method: 'get',
params: {
id: id.toString()
}
})
}
// 新增分类地位
export function addClassifyStatuse(data:object) {
return request({
url: '/api/v1/businesses/classifyStatuse/add',
method: 'post',
data: data
})
}
// 修改分类地位
export function updateClassifyStatuse(data:object) {
return request({
url: '/api/v1/businesses/classifyStatuse/edit',
method: 'put',
data: data
})
}
// 删除分类地位
export function delClassifyStatuse(ids:number[],version:number[]) {
return request({
url: '/api/v1/businesses/classifyStatuse/delete',
method: 'delete',
data:{
ids:ids,
version:version
}
})
}
export function auditSpeciesName(data:object) {
return request({
url: '/api/v1/businesses/classifyStatuse/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,682 @@
<template>
<div class="businesses-classifyStatuse-container">
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
ref="queryFormRef"
/>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
v-auth="'api/v1/businesses/classifyStatuse/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/classifyStatuse/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/classifyStatuse/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/classifyStatuse/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/classifyStatuse/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import {
listClassifyStatuse,
delClassifyStatuse,
addClassifyStatuse,
updateClassifyStatuse,
auditSpeciesName,
} from './api';
import {
ClassifyStatuseTableColumns,
ClassifyStatuseInfoData,
ClassifyStatuseTableDataState,
} from './type';
import { SpeciesNameInfoData } from '../speciesName/type';
import { listSpeciesName } from '../speciesName/api';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
defineOptions({ name: 'ClassifyStatuse' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<ClassifyStatuseTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '界', prop: 'jie' },
{ label: 'Kingdom', prop: 'kingdom', minWidth: 120 },
{ label: '门', prop: 'men' },
{ label: 'Phylum', prop: 'phylum' },
{ label: '纲', prop: 'gang' },
{ label: 'Class', prop: 'classTitle' },
{ label: '目', prop: 'mu' },
{ label: 'Order', prop: 'orderTitle' },
{ label: '科', prop: 'ke' },
{ label: 'Family', prop: 'family' },
{ label: '属', prop: 'shu' },
{ label: 'Genus', prop: 'genus' },
{ label: '种', prop: 'zhong' },
{ label: 'Species', prop: 'species' },
{ label: '其他分类信息', prop: 'otherInfo', minWidth: 120 },
{ label: '数据来源', prop: 'sourcesData' },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
minWidth: 120,
formater(_: ClassifyStatuseTableColumns, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
minWidth: 120,
isFormater: true,
formater(_: ClassifyStatuseTableColumns, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
{ label: '核查意见', prop: 'auditView' },
{ label: '备注', prop: 'remark' },
];
const onSearch = async (val: Record<string, any>) => {
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v]) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
getList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
getList();
};
const searchForm = ref<Partial<ClassifyStatuseInfoData>>({
speciesCode: undefined,
jie: undefined,
kingdom: undefined,
men: undefined,
phylum: undefined,
gang: undefined,
classTitle: undefined,
mu: undefined,
orderTitle: undefined,
ke: undefined,
family: undefined,
shu: undefined,
genus: undefined,
zhong: undefined,
species: undefined,
otherInfo: undefined,
sourcesData: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{ label: '界', prop: 'jie', type: 'input', placeholder: '请输入界' },
{ label: 'kingdom', prop: 'kingdom', type: 'input', placeholder: '请输入kingdom' },
{ label: '门', prop: 'men', type: 'input', placeholder: '请输入门' },
{ label: 'Phylum', prop: 'phylum', type: 'input', placeholder: '请输入Phylum' },
{ label: '纲', prop: 'gang', type: 'input', placeholder: '请输入纲' },
{ label: 'Class', prop: 'classTitle', type: 'input', placeholder: '请输入Class' },
{ label: '目', prop: 'mu', type: 'input', placeholder: '请输入目' },
{ label: 'Order', prop: 'orderTitle', type: 'input', placeholder: '请输入Order' },
{ label: '科', prop: 'ke', type: 'input', placeholder: '请输入科' },
{ label: 'Family', prop: 'family', type: 'input', placeholder: '请输入Family' },
{ label: '属', prop: 'shu', type: 'input', placeholder: '请输入属' },
{ label: 'Genus', prop: 'genus', type: 'input', placeholder: '请输入Genus' },
{ label: '种', prop: 'zhong', type: 'input', placeholder: '请输入种' },
{ label: 'Species', prop: 'species', type: 'input', placeholder: '请输入Species' },
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
getList();
reqGetUserList();
reqListSpeciesName();
};
//
const getList = async () => {
loading.value = true;
const res = await listClassifyStatuse(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const specieslist = ref<SpeciesNameInfoData[]>([]);
/**获取物种列表 */
const reqListSpeciesName = async () => {
if (userOptions.value.length > 0) return;
const res = await listSpeciesName({ pageSize: 9999, pageNum: 1 });
specieslist.value = res.data.list;
};
/**获取用户列表 */
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<ClassifyStatuseInfoData>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'select',
options: specieslist.value.map((i) => {
return {
value: i.speciesCode,
label: i.speciesCode,
};
}),
span: 12,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请选择物种编码',
},
},
{
label: '界',
prop: 'jie',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入界', trigger: 'blur' }],
componentProps: {
placeholder: '请输入界',
},
},
{
label: 'kingdom',
prop: 'kingdom',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入kingdom', trigger: 'blur' }],
componentProps: {
placeholder: '请输入界',
},
},
{
label: '门',
prop: 'men',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入门', trigger: 'blur' }],
componentProps: {
placeholder: '请输入门',
},
},
{
label: 'Phylum',
prop: 'phylum',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Phylum', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Phylum',
},
},
{
label: '纲',
prop: 'gang',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入纲', trigger: 'blur' }],
componentProps: {
placeholder: '请输入纲',
},
},
{
label: 'Class',
prop: 'classTitle',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Class', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Class',
},
},
{
label: '目',
prop: 'mu',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入目', trigger: 'blur' }],
componentProps: {
placeholder: '请输入目',
},
},
{
label: 'Order',
prop: 'orderTitle',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Order', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Order',
},
},
{
label: '科',
prop: 'ke',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入科', trigger: 'blur' }],
componentProps: {
placeholder: '请输入科',
},
},
{
label: 'Family',
prop: 'family',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Family', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Family',
},
},
{
label: '属',
prop: 'shu',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入属', trigger: 'blur' }],
componentProps: {
placeholder: '请输入属',
},
},
{
label: 'Genus',
prop: 'genus',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Genus', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Genus',
},
},
{
label: '种',
prop: 'zhong',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入种', trigger: 'blur' }],
componentProps: {
placeholder: '请输入种',
},
},
{
label: 'Species',
prop: 'species',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入Species', trigger: 'blur' }],
componentProps: {
placeholder: '请输入Species',
},
},
{
label: '数据来源',
prop: 'sourcesData',
span: 12,
type: 'input',
rules: [{ required: true, message: '请输入数据来源', trigger: 'blur' }],
componentProps: {
placeholder: '请输入数据来源',
},
},
{
label: '其他分类信息',
prop: 'otherInfo',
span: 24,
type: 'input',
rules: [{ required: true, message: '请输入其他分类信息', trigger: 'blur' }],
componentProps: {
placeholder: '请输入其他信息',
type: 'textarea',
},
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
// rules: [{ required: true, message: '', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<ClassifyStatuseInfoData>({
speciesCode: undefined,
jie: undefined,
kingdom: undefined,
men: undefined,
phylum: undefined,
gang: undefined,
classTitle: undefined,
mu: undefined,
orderTitle: undefined,
ke: undefined,
family: undefined,
shu: undefined,
genus: undefined,
id: undefined,
zhong: undefined,
species: undefined,
otherInfo: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
remark: undefined,
auditStatus: undefined,
auditUser: undefined,
auditDate: undefined,
version: undefined,
auditView: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: ClassifyStatuseInfoData) => {
if (formType.value === 'add') {
addClassifyStatuse(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
getList();
});
} else {
form.auditStatus = 0;
updateClassifyStatuse(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
getList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
speciesCode: undefined,
jie: undefined,
kingdom: undefined,
men: undefined,
phylum: undefined,
gang: undefined,
classTitle: undefined,
mu: undefined,
orderTitle: undefined,
ke: undefined,
family: undefined,
shu: undefined,
genus: undefined,
zhong: undefined,
species: undefined,
otherInfo: undefined,
sourcesData: undefined,
id: undefined,
createUser: undefined,
createDate: undefined,
remark: undefined,
auditStatus: undefined,
auditUser: undefined,
auditDate: undefined,
version: undefined,
auditView: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增分类地位';
formType.value = 'add';
};
const handleUpdate = (row: ClassifyStatuseInfoData) => {
showDialog.value = true;
formTitle.value = '编辑分类地位';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: ClassifyStatuseTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delClassifyStatuse(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
getList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: ClassifyStatuseTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: ClassifyStatuseTableColumns) => {
auditSpeciesName(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
getList();
});
};
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2 {
margin: 3px;
}
</style>

View File

@ -0,0 +1,78 @@
export interface ClassifyStatuseTableColumns {
id: number; //
speciesCode: string; // 物种编码
jie: string; // 界
kingdom: string; // Kingdom
men: string; // 门
phylum: string; // Phylum
gang: string; // 纲
classTitle: string; // Class
mu: string; // 目
orderTitle: string; // Order
ke: string; // 科
family: string; // Family
shu: string; // 属
genus: string; // Genus
zhong: string; // Genus
species: string; // Species
otherInfo: string; // 其他分类信息
sourcesData: string; // 数据来源
createUser: number; // 数据采集人
createDate: string; // 数据采集日期
auditUser: number; // 数据核查人
auditDate: string; // 数据核查日期
auditStatus: number; // 核查
auditView: string; // 核查意见
remark: string; // 备注
version: number; // 版本
createdAt: string; //
}
export interface ClassifyStatuseInfoData {
id?: number | undefined; //
speciesCode: string | undefined; // 物种编码
jie: string | undefined; // 界
kingdom: string | undefined; // Kingdom
men: string | undefined; // 门
phylum: string | undefined; // Phylum
gang: string | undefined; // 纲
classTitle: string | undefined; // Class
mu: string | undefined; // 目
orderTitle: string | undefined; // Order
ke: string | undefined; // 科
family: string | undefined; // Family
shu: string | undefined; // 属
genus: string | undefined; // Genus
zhong: string | undefined; // Genus
species: string | undefined; // Species
otherInfo: string | undefined; // 其他分类信息
sourcesData: string | undefined; // 数据来源
createUser: number | undefined; // 数据采集人
createDate: string | undefined; // 数据采集日期
auditUser: number | undefined; // 数据核查人
auditDate: string | undefined; // 数据核查日期
auditStatus: number | undefined; // 核查
auditView: string | undefined; // 核查意见
remark: string | undefined; // 备注
version: number | undefined; // 版本
}
export interface ClassifyStatuseTableDataState {
ids: any[];
tableData: {
data: Array<ClassifyStatuseTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface ClassifyStatuseEditState {
loading: boolean;
isShowDialog: boolean;
formData: ClassifyStatuseInfoData;
rules: object;
}

View File

@ -0,0 +1,53 @@
import request from '/@/utils/request'
// 查询国内分布列表
export function listDomestiDistribuion(query:object) {
return request({
url: '/api/v1/businesses/domestiDistribuion/list',
method: 'get',
params: query
})
}
// 查询国内分布详细
export function getDomestiDistribuion(id:number) {
return request({
url: '/api/v1/businesses/domestiDistribuion/get',
method: 'get',
params: {
id: id.toString()
}
})
}
// 新增国内分布
export function addDomestiDistribuion(data:object) {
return request({
url: '/api/v1/businesses/domestiDistribuion/add',
method: 'post',
data: data
})
}
// 修改国内分布
export function updateDomestiDistribuion(data:object) {
return request({
url: '/api/v1/businesses/domestiDistribuion/edit',
method: 'put',
data: data
})
}
// 删除国内分布
export function delDomestiDistribuion(ids:number[],version: number[]) {
return request({
url: '/api/v1/businesses/domestiDistribuion/delete',
method: 'delete',
data:{
ids:ids,
version: version,
}
})
}
export function auditDomestiDistribuion(data: object) {
return request({
url: '/api/v1/businesses/domestiDistribuion/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,519 @@
<template>
<div class="businesses-domestiDistribuion-container">
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
ref="queryFormRef"
/>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
v-auth="'api/v1/businesses/domestiDistribuion/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/domestiDistribuion/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/domestiDistribuion/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/domestiDistribuion/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/domestiDistribuion/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import {
listDomestiDistribuion,
getDomestiDistribuion,
delDomestiDistribuion,
addDomestiDistribuion,
updateDomestiDistribuion,
auditDomestiDistribuion,
} from './api';
import {
DomestiDistribuionTableColumns,
DomestiDistribuionInfoData,
DomestiDistribuionTableDataState,
} from './type';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
import { listSpeciesName } from '../speciesName/api';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
import { SpeciesNameInfoData } from '../speciesName/type';
defineOptions({ name: 'BusinessesDomestiDistribuionList' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<DomestiDistribuionTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '所属省区', prop: 'province', minWidth: 150 },
{ label: '所属地级州市', prop: 'city', minWidth: 150 },
{ label: '所属区县', prop: 'county', minWidth: 150 },
{ label: '发现年份', prop: 'year', minWidth: 150 },
{ label: '数据来源', prop: 'sourcesData', minWidth: 150 },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
minWidth: 120,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
minWidth: 120,
isFormater: true,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
{ label: '核查意见', prop: 'auditView' },
{ label: '备注', prop: 'remark' },
];
const onSearch = async (val: Record<string, any>) => {
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v] || val[v] === 0) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
getList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
getList();
};
const searchForm = ref<Partial<DomestiDistribuionInfoData>>({
speciesCode: undefined,
province: undefined,
city: undefined,
county: undefined,
year: undefined,
sourcesData: undefined,
createUser: undefined,
auditStatus: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{ label: '所属省区', prop: 'province', type: 'input', placeholder: '请输入所属省区' },
{ label: '所属地级州市', prop: 'city', type: 'input', placeholder: '请输入所属地级州市' },
{ label: '所属区县', prop: 'county', type: 'input', placeholder: '请输入所属区县' },
{ label: '发现年份', prop: 'year', type: 'input', placeholder: '请输入发现年份' },
{ label: '数据来源', prop: 'sourcesData', type: 'input', placeholder: '请输入数据来源' },
{
label: '核查状态',
prop: 'auditStatus',
type: 'select',
options: [
{ label: '待审核', value: 0 },
{ label: '通过', value: 1 },
{ label: '不通过', value: 2 },
],
placeholder: '请输入物种编码',
},
{
label: '数据采集人',
prop: 'createUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据采集人',
},
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
getList();
reqGetUserList();
reqListSpeciesName();
};
//
const getList = async () => {
loading.value = true;
const res = await listDomestiDistribuion(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const specieslist = ref<SpeciesNameInfoData[]>([]);
/**获取物种列表 */
const reqListSpeciesName = async () => {
if (userOptions.value.length > 0) return;
const res = await listSpeciesName({ pageSize: 9999, pageNum: 1 });
specieslist.value = res.data.list;
};
/**获取用户列表 */
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<DomestiDistribuionTableColumns>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'select',
options: specieslist.value.map((i) => {
return {
value: i.speciesCode,
label: i.speciesCode,
};
}),
span: 24,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请选择物种编码',
},
},
{
label: '所属省区',
prop: 'province',
rules: [{ required: true, message: '请输入所属省区', trigger: 'blur' }],
type: 'input',
span: 24,
componentProps: {
placeholder: '请输入所属省区',
},
},
{
label: '所属地级州市',
prop: 'city',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入所属地级州市', trigger: 'blur' }],
componentProps: {
placeholder: '请输入所属地级州市',
},
},
{
label: '所属区县',
prop: 'county',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入所属区县', trigger: 'blur' }],
componentProps: {
placeholder: '请输入所属区县',
},
},
{
label: '发现年份',
prop: 'year',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入发现年份', trigger: 'blur' }],
componentProps: {
placeholder: '请输入发现年份',
},
},
{
label: '数据来源',
prop: 'sourcesData',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入数据来源', trigger: 'blur' }],
componentProps: {
placeholder: '请输入数据来源',
},
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
rules: [{ required: true, message: '请选择数据采集人', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<Partial<DomestiDistribuionInfoData>>({
speciesCode: undefined,
province: undefined,
county: undefined,
city: undefined,
year: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
remark: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: DomestiDistribuionInfoData) => {
if (formType.value === 'add') {
addDomestiDistribuion(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
getList();
});
} else {
form.auditStatus = 0;
updateDomestiDistribuion(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
getList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
speciesCode: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增分类地位';
formType.value = 'add';
};
const handleUpdate = (row: DomestiDistribuionInfoData) => {
showDialog.value = true;
formTitle.value = '编辑分类地位';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: DomestiDistribuionTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delDomestiDistribuion(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
getList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: DomestiDistribuionTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: DomestiDistribuionTableColumns) => {
auditDomestiDistribuion(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
getList();
});
};
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2 {
margin: 3px;
}
</style>

View File

@ -0,0 +1,55 @@
export interface DomestiDistribuionTableColumns {
id: number; //
speciesCode: string; // 物种编码
province: string; // 所属省区
city: string; // 所属地级州市
county: string; // 所属区县
year: string; // 发现年份
sourcesData: string; // 数据来源
createUser: number; // 数据采集人
createDate: string; // 数据采集日期
auditUser: number; // 数据核查人
auditDate: string; // 数据核查日期
auditStatus: number; //
auditView: string; //
remark: string; //
version: number; //
}
export interface DomestiDistribuionInfoData {
id: number | undefined; //
speciesCode: string | undefined; // 物种编码
province: string | undefined; // 所属省区
city: string | undefined; // 所属地级州市
county: string | undefined; // 所属区县
year: string | undefined; // 发现年份
sourcesData: string | undefined; // 数据来源
createUser: number | undefined; // 数据采集人
createDate: string | undefined; // 数据采集日期
auditUser: number | undefined; // 数据核查人
auditDate: string | undefined; // 数据核查日期
auditStatus: number | undefined; //
auditView: string | undefined; //
remark: string | undefined; //
version: number | undefined; //
}
export interface DomestiDistribuionTableDataState {
ids: any[];
tableData: {
data: Array<DomestiDistribuionTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface DomestiDistribuionEditState {
loading: boolean;
isShowDialog: boolean;
formData: DomestiDistribuionInfoData;
rules: object;
}

View File

@ -0,0 +1,53 @@
import request from '/@/utils/request'
// 查询国外分布列表
export function listForeignDistribution(query:object) {
return request({
url: '/api/v1/businesses/foreignDistribution/list',
method: 'get',
params: query
})
}
// 查询国外分布详细
export function getForeignDistribution(id:number) {
return request({
url: '/api/v1/businesses/foreignDistribution/get',
method: 'get',
params: {
id: id.toString()
}
})
}
// 新增国外分布
export function addForeignDistribution(data:object) {
return request({
url: '/api/v1/businesses/foreignDistribution/add',
method: 'post',
data: data
})
}
// 修改国外分布
export function updateForeignDistribution(data:object) {
return request({
url: '/api/v1/businesses/foreignDistribution/edit',
method: 'put',
data: data
})
}
// 删除国外分布
export function delForeignDistribution(ids:number[],version: number[]) {
return request({
url: '/api/v1/businesses/foreignDistribution/delete',
method: 'delete',
data:{
ids:ids,
version: version,
}
})
}
export function auditForeignDistribution(data: object) {
return request({
url: '/api/v1/businesses/foreignDistribution/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,520 @@
<template>
<div class="businesses-foreignDistribution-container">
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
ref="queryFormRef"
/>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
v-auth="'api/v1/businesses/foreignDistribution/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/foreignDistribution/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/foreignDistribution/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/foreignDistribution/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/foreignDistribution/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import {
listForeignDistribution,
delForeignDistribution,
addForeignDistribution,
updateForeignDistribution,
auditForeignDistribution,
} from './api';
import {
ForeignDistributionTableColumns,
ForeignDistributionInfoData,
ForeignDistributionTableDataState,
} from './type';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
import { listSpeciesName } from '../speciesName/api';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
import { SpeciesNameInfoData } from '../speciesName/type';
defineOptions({ name: 'BusinessesForeignDistributionList' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<ForeignDistributionTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '所属大洲', prop: 'continent', minWidth: 150 },
{ label: '所属国家/地区', prop: 'region', minWidth: 150 },
{ label: '所属州', prop: 'belongState', minWidth: 150 },
{ label: '发现/报道年份', prop: 'year', minWidth: 150 },
{ label: '参考文献', prop: 'references', minWidth: 150 },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
minWidth: 120,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
minWidth: 120,
isFormater: true,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
{ label: '核查意见', prop: 'auditView' },
{ label: '备注', prop: 'remark' },
];
const onSearch = async (val: Record<string, any>) => {
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v] || val[v] === 0) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
getList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
getList();
};
const searchForm = ref<Partial<ForeignDistributionInfoData>>({
speciesCode: undefined,
continent: undefined,
region: undefined,
belongState: undefined,
year: undefined,
references: undefined,
createUser: undefined,
auditStatus: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{ label: '所属大洲', prop: 'continent', type: 'input', placeholder: '请输入所属大洲' },
{ label: '所属国家/地区', prop: 'region', type: 'input', placeholder: '请输入所属国家/地区' },
{ label: '所属州', prop: 'belongState', type: 'input', placeholder: '请输入所属州' },
{ label: '发现/报道年份', prop: 'year', type: 'input', placeholder: '请输入发现/报道年份' },
{ label: '参考文献', prop: 'references', type: 'input', placeholder: '请输入参考文献' },
{
label: '核查状态',
prop: 'auditStatus',
type: 'select',
options: [
{ label: '待审核', value: 0 },
{ label: '通过', value: 1 },
{ label: '不通过', value: 2 },
],
placeholder: '请输入物种编码',
},
{
label: '数据采集人',
prop: 'createUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据采集人',
},
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
getList();
reqGetUserList();
reqListSpeciesName();
};
//
const getList = async () => {
loading.value = true;
const res = await listForeignDistribution(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const specieslist = ref<SpeciesNameInfoData[]>([]);
/**获取物种列表 */
const reqListSpeciesName = async () => {
if (userOptions.value.length > 0) return;
const res = await listSpeciesName({ pageSize: 9999, pageNum: 1 });
specieslist.value = res.data.list;
};
/**获取用户列表 */
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<ForeignDistributionInfoData>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'select',
options: specieslist.value.map((i) => {
return {
value: i.speciesCode,
label: i.speciesCode,
};
}),
span: 24,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请选择物种编码',
},
},
{
label: '所属大洲',
prop: 'continent',
rules: [{ required: true, message: '请输入所属大洲', trigger: 'blur' }],
type: 'input',
span: 24,
componentProps: {
placeholder: '请输入所属大洲',
},
},
{
label: '所属国家/地区',
prop: 'region',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入所属国家/地区', trigger: 'blur' }],
componentProps: {
placeholder: '请输入所属国家/地区',
},
},
{
label: '所属州',
prop: 'belongState',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入所属州', trigger: 'blur' }],
componentProps: {
placeholder: '请输入所属州',
},
},
{
label: '发现/报道年份',
prop: 'year',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入发现/报道年份', trigger: 'blur' }],
componentProps: {
placeholder: '请输入发现/报道年份',
},
},
{
label: '参考文献',
prop: 'references',
type: 'input',
span: 24,
rules: [{ required: true, message: '请输入参考文献', trigger: 'blur' }],
componentProps: {
placeholder: '请输入参考文献',
},
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
rules: [{ required: true, message: '请选择数据采集人', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<Partial<ForeignDistributionInfoData>>({
speciesCode: undefined,
continent: undefined,
region: undefined,
belongState: undefined,
year: undefined,
references: undefined,
createUser: undefined,
createDate: undefined,
remark: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: ForeignDistributionInfoData) => {
if (formType.value === 'add') {
addForeignDistribution(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
getList();
});
} else {
form.auditStatus = 0;
updateForeignDistribution(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
getList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
speciesCode: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增分类地位';
formType.value = 'add';
};
const handleUpdate = (row: ForeignDistributionInfoData) => {
showDialog.value = true;
formTitle.value = '编辑分类地位';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: ForeignDistributionTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delForeignDistribution(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
getList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: ForeignDistributionTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: ForeignDistributionTableColumns) => {
auditForeignDistribution(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
getList();
});
};
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2 {
margin: 3px;
}
</style>

View File

@ -0,0 +1,55 @@
export interface ForeignDistributionTableColumns {
id: number; //
speciesCode: string; // 物种编码
continent: string; // 所属大洲
region: string; // 所属国家/地区
belongState: string; // 所属州
year: string; // 发现/报道年份
references: string; // 参考文献
createUser: number; // 数据采集人
createDate: string; // 数据采集日期
auditUser: number; // 数据核查人
auditDate: string; // 数据核查日期
auditStatus: number; //
auditView: string; //
remark: string; //
version: number; //
}
export interface ForeignDistributionInfoData {
id: number | undefined; //
speciesCode: string | undefined; // 物种编码
continent: string | undefined; // 所属大洲
region: string | undefined; // 所属国家/地区
belongState: string | undefined; // 所属州
year: string | undefined; // 发现/报道年份
references: string | undefined; // 参考文献
createUser: number | undefined; // 数据采集人
createDate: string | undefined; // 数据采集日期
auditUser: number | undefined; // 数据核查人
auditDate: string | undefined; // 数据核查日期
auditStatus: number | undefined; //
auditView: string | undefined; //
remark: string | undefined; //
version: number | undefined; //
}
export interface ForeignDistributionTableDataState {
ids: any[];
tableData: {
data: Array<ForeignDistributionTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface ForeignDistributionEditState {
loading: boolean;
isShowDialog: boolean;
formData: ForeignDistributionInfoData;
rules: object;
}

View File

@ -0,0 +1,53 @@
import request from '/@/utils/request';
// 查询地理分布列表
export function listGeography(query: object) {
return request({
url: '/api/v1/businesses/geography/list',
method: 'get',
params: query,
});
}
// 查询地理分布详细
export function getGeography(id: number) {
return request({
url: '/api/v1/businesses/geography/get',
method: 'get',
params: {
id: id.toString(),
},
});
}
// 新增地理分布
export function addGeography(data: object) {
return request({
url: '/api/v1/businesses/geography/add',
method: 'post',
data: data,
});
}
// 修改地理分布
export function updateGeography(data: object) {
return request({
url: '/api/v1/businesses/geography/edit',
method: 'put',
data: data,
});
}
// 删除地理分布
export function delGeography(ids: number[], version: number[]) {
return request({
url: '/api/v1/businesses/geography/delete',
method: 'delete',
data: {
ids: ids,
version: version,
},
});
}
export function auditGeography(data: object) {
return request({
url: '/api/v1/businesses/geography/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,512 @@
<template>
<div class="businesses-geography-container">
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
ref="queryFormRef"
/>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" @click="handleAdd" v-auth="'api/v1/businesses/geography/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/geography/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/geography/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/geography/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/geography/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed, toRaw } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { listGeography, delGeography, addGeography, updateGeography, auditGeography } from './api';
import { GeographyTableColumns, GeographyInfoData, GeographyTableDataState } from './type';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
import { listSpeciesName } from '../speciesName/api';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
import {SpeciesNameInfoData} from '../speciesName/type'
defineOptions({ name: 'BusinessesGeographyList' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<GeographyTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '地理分布描述', prop: 'distributionInfo', minWidth: 150 },
{ label: '原产地描述', prop: 'originInfo', minWidth: 150 },
{ label: '国外分布描述', prop: 'abroadInfo', minWidth: 150 },
{ label: '国内分布描述', prop: 'domesticInfo', minWidth: 150 },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
minWidth: 120,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
minWidth: 120,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
minWidth: 120,
isFormater: true,
formater(_, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
{ label: '核查意见', prop: 'auditView' },
{ label: '备注', prop: 'remark' },
];
const onSearch = async (val: Record<string, any>) => {
console.log(val);
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v]||val[v]===0) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
getList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
getList();
};
const searchForm = ref<Partial<GeographyInfoData>>({
speciesCode: undefined,
createUser: undefined,
createDate: undefined,
auditUser: undefined,
auditDate: undefined,
auditStatus: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{
label: '核查状态',
prop: 'auditStatus',
type: 'select',
options: [
{ label: '待审核', value: 0 },
{ label: '通过', value: 1 },
{ label: '不通过', value: 2 },
],
placeholder: '请输入物种编码',
},
{
label: '数据采集人',
prop: 'createUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据采集人',
},
{ label: '数据采集日期', prop: 'createDate', type: 'daterange', valueFormat: 'YYYY-MM-DD' },
{
label: '数据核查人',
prop: 'auditUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据核查人',
},
{ label: '数据核查日期', prop: 'auditDate', type: 'daterange', valueFormat: 'YYYY-MM-DD' },
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
getList();
reqGetUserList();
reqListSpeciesName();
};
//
const getList = async () => {
loading.value = true;
const res = await listGeography(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const specieslist = ref<SpeciesNameInfoData[]>([]);
/**获取物种列表 */
const reqListSpeciesName = async () => {
if (userOptions.value.length > 0) return;
const res = await listSpeciesName({ pageSize: 9999, pageNum: 1 });
specieslist.value = res.data.list;
};
/**获取用户列表 */
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<GeographyInfoData>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'select',
options: specieslist.value.map((i) => {
return {
value: i.speciesCode,
label: i.speciesCode,
};
}),
span: 24,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请选择物种编码',
},
},
{
label:"地理分布描述",
prop:'distributionInfo',
rules: [{ required: true, message: '请输入地理分布描述', trigger: 'blur' }],
type:'input',
span:24,
componentProps:{
type:'textarea',
placeholder:'请输入地理分布描述'
}
},
{
label:"原产地描述",
prop:'originInfo',
type:'input',
span:24,
rules: [{ required: true, message: '请输入原产地描述', trigger: 'blur' }],
componentProps:{
type:'textarea',
placeholder:'请输入原产地描述'
}
},
{
label:"国外分布描述",
prop:'abroadInfo',
type:'input',
span:24,
rules: [{ required: true, message: '请输入国外分布描述', trigger: 'blur' }],
componentProps:{
type:'textarea',
placeholder:'请输入国外分布描述'
}
},
{
label:"国内分布描述",
prop:'domesticInfo',
type:'input',
span:24,
rules: [{ required: true, message: '请输入国内分布描述', trigger: 'blur' }],
componentProps:{
type:'textarea',
placeholder:'请输入国内分布描述'
}
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
rules: [{ required: true, message: '请选择数据采集人', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<Partial<GeographyInfoData>>({
speciesCode: undefined,
distributionInfo: undefined,
originInfo: undefined,
abroadInfo: undefined,
domesticInfo: undefined,
createUser: undefined,
createDate: undefined,
auditStatus: undefined,
remark: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: GeographyInfoData) => {
if (formType.value === 'add') {
addGeography(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
getList();
});
} else {
form.auditStatus = 0;
updateGeography(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
getList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
speciesCode: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增分类地位';
formType.value = 'add';
};
const handleUpdate = (row: GeographyInfoData) => {
showDialog.value = true;
formTitle.value = '编辑分类地位';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: GeographyTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delGeography(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
getList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: GeographyTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: GeographyTableColumns) => {
auditGeography(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
getList();
});
};
</script>
<style lang="scss" scoped>
.colBlock {
display: block;
}
.colNone {
display: none;
}
.ml-2 {
margin: 3px;
}
</style>

View File

@ -0,0 +1,54 @@
export interface GeographyTableColumns {
id: number; //
speciesCode: string; // 物种编码
distributionInfo: string; // 地理分布描述
originInfo: string; // 原产地描述
abroadInfo: string; // 国外分布描述
domesticInfo: string; // 国内分布描述
createUser: number; // 数据采集人
createDate: string; // 数据采集日期
auditUser: number; // 数据核查人
auditDate: string; // 数据核查日期
auditStatus: number; // 核查状态
auditView: string; // 核查意见
remark: string; // 备注
version: number; //
createdAt: string; //
}
export interface GeographyInfoData {
id: number | undefined; //
speciesCode: string | undefined; // 物种编码
distributionInfo: string | undefined; // 地理分布描述
originInfo: string | undefined; // 原产地描述
abroadInfo: string | undefined; // 国外分布描述
domesticInfo: string | undefined; // 国内分布描述
createUser: number | undefined; // 数据采集人
createDate: string | undefined; // 数据采集日期
auditUser: number | undefined; // 数据核查人
auditDate: string | undefined; // 数据核查日期
auditStatus: number | undefined; // 核查状态
auditView: string | undefined; // 核查意见
remark: string | undefined; // 备注
version: number | undefined; //
}
export interface GeographyTableDataState {
ids: any[];
tableData: {
data: Array<GeographyTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface GeographyEditState {
loading: boolean;
isShowDialog: boolean;
formData: GeographyInfoData;
rules: object;
}

View File

@ -0,0 +1,54 @@
import request from '/@/utils/request';
// 查询列表
export function listSpeciesName(query: object) {
return request({
url: '/api/v1/businesses/speciesName/list',
method: 'get',
params: query,
});
}
// 查询详细
export function getSpeciesName(id: number) {
return request({
url: '/api/v1/businesses/speciesName/get',
method: 'get',
params: {
id: id.toString(),
},
});
}
// 新增
export function addSpeciesName(data: object) {
return request({
url: '/api/v1/businesses/speciesName/add',
method: 'post',
data: data,
});
}
// 修改
export function updateSpeciesName(data: object) {
return request({
url: '/api/v1/businesses/speciesName/edit',
method: 'put',
data: data,
});
}
// 删除
export function delSpeciesName(ids: number[], version: number[]) {
return request({
url: '/api/v1/businesses/speciesName/delete',
method: 'delete',
data: {
ids: ids,
version: version,
},
});
}
export function auditSpeciesName(data:object) {
return request({
url: '/api/v1/businesses/speciesName/audit',
method: 'post',
data: data,
});
}

View File

@ -0,0 +1,505 @@
<template>
<div>
<el-card shadow="hover">
<QueryForm
v-model="searchForm"
:fields="searchFields"
@search="onSearch"
@reset="onReset"
ref="queryFormRef"
/>
<el-row :gutter="10" style="padding-bottom: 10px">
<el-col :span="1.5">
<el-button type="primary" @click="handleAdd" v-auth="'api/v1/businesses/speciesName/add'"
><el-icon><ele-Plus /></el-icon>新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
:disabled="multiple"
@click="handleDelete(null)"
v-auth="'api/v1/businesses/speciesName/delete'"
><el-icon><ele-Delete /></el-icon>删除</el-button
>
</el-col>
</el-row>
<ProTable
v-if="columns.length > 0"
ref="proTableRef"
:columns="columns"
:data="tableData.data"
:loading="loading"
:show-selection="true"
@selection-change="handleSelectionChange"
:heightOffset="400"
:action-width="280"
:show-expand="true"
>
<template #expand="{ row }">
<el-form-item label="核查意见">
{{ row.auditView }}
</el-form-item>
<el-form-item label="备注">
{{ row.remark }}
</el-form-item>
</template>
<template #actions="{ row }">
<el-button
type="primary"
@click="handleUpdate(row)"
size="small"
v-auth="'api/v1/businesses/speciesName/edit'"
v-if="row.auditStatus != 1"
><el-icon><ele-EditPen /></el-icon>修改</el-button
>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
v-auth="'api/v1/businesses/speciesName/delete'"
><el-icon><ele-DeleteFilled /></el-icon>删除</el-button
>
<el-button
type="warning"
size="small"
@click="handleAudit(row)"
v-if="row.auditStatus == 0"
v-auth="'api/v1/businesses/speciesName/audit'"
><el-icon><ele-Check /></el-icon>审核</el-button
>
</template>
</ProTable>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="speciesNameList"
/>
</el-card>
<ModalForm
v-model:visible="showDialog"
:title="formTitle"
:fields="formFields"
v-model:modelValue="formData"
:type="formType"
:show-footer="true"
:label-width="'120px'"
:width="'50%'"
@submit="handleFormSubmit"
@cancel="handleCancel"
ref="modalFormRef"
>
</ModalForm>
<AuditForm
ref="auditFormRef"
v-model="showAuditDialog"
title="审核数据"
@submit="onAuditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, reactive, onMounted, ref, computed } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import {
listSpeciesName,
delSpeciesName,
addSpeciesName,
updateSpeciesName,
auditSpeciesName,
} from './api';
import {
SpeciesNameTableColumns,
SpeciesNameInfoData,
SpeciesNameTableDataState,
} from '/@/views/businesses/speciesName/type';
import QueryForm from '/@/components/dynamicpage/queryForm/index.vue';
import ProTable from '/@/components/dynamicpage/ProTable/index.vue';
import ModalForm from '/@/components/dynamicpage/modalForm/index.vue';
import AuditForm from '/@/components/dynamicpage/auditForm/index.vue';
import { QueryFormField, TableColumn, PopupFormField } from '/@/components/dynamicpage/type';
import { getUserList } from '/@/api/system/user/index';
import { UserItem, VersionMap } from '/@/types';
import { parseTime } from '/@/utils/gfast';
import { cloneDeep } from 'lodash';
defineOptions({ name: 'apiV1DemoSpeciesNameList' });
const loading = ref(false);
//
const single = ref(true);
//
const multiple = ref(true);
const userOptions = ref<UserItem[]>([]);
const state = reactive<SpeciesNameTableDataState>({
ids: [],
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
const versionMap = ref<VersionMap>({} as VersionMap);
const proTableRef = ref();
const { tableData } = toRefs(state);
const columns: TableColumn[] = [
{ label: '物种编码', prop: 'speciesCode' },
{ label: '物种名称', prop: 'name' },
{ label: '物种名称内容', prop: 'content' },
{ label: '数据来源', prop: 'sourcesData' },
{
label: '数据采集人',
prop: 'createUser',
isFormater: true,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据采集日期',
prop: 'createDate',
isFormater: true,
formater(_: SpeciesNameTableColumns, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '数据核查人',
prop: 'auditUser',
isFormater: true,
formater(row, column, cellValue, index) {
const user = userOptions.value.find((i) => i.id === cellValue);
return user ? user.userNickname : '';
},
},
{
label: '数据核查日期',
prop: 'auditDate',
isFormater: true,
formater(_: SpeciesNameTableColumns, col: any, val: string) {
return parseTime(val, '{y}-{m}-{d}');
},
},
{
label: '状态',
prop: 'auditStatus',
isFormater: true,
formater(row, column, cellValue, index) {
return cellValue == 0 ? '待审核' : cellValue == 1 ? '通过' : '不通过';
},
},
];
const onSearch = async (val: Record<string, any>) => {
let newVal: Record<string, any> = {};
for (const v in val) {
if (val[v]) {
newVal[v] = val[v];
}
}
state.tableData.param = { ...state.tableData.param, ...newVal };
speciesNameList();
};
const onReset = () => {
state.tableData.param = {
pageNum: 1,
pageSize: 10,
};
speciesNameList();
};
const searchForm = ref<Omit<SpeciesNameInfoData, 'id' | 'remark' | 'content'>>({
speciesCode: undefined,
name: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
auditUser: undefined,
auditDate: undefined,
});
const searchFields = computed<QueryFormField[]>(() => [
{ label: '物种编码', prop: 'speciesCode', type: 'input', placeholder: '请输入物种编码' },
{ label: '物种名称', prop: 'name', type: 'input', placeholder: '请输入物种名称' },
{ label: '数据来源', prop: 'sourcesData', type: 'input', placeholder: '请输入数据来源' },
{
label: '数据采集人',
prop: 'createUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据采集人',
},
{ label: '数据采集日期', prop: 'createDate', type: 'daterange', valueFormat: 'YYYY-MM-DD' },
{
label: '数据核查人',
prop: 'auditUser',
type: 'select',
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
placeholder: '请选择数据核查人',
},
{ label: '数据核查日期', prop: 'auditDate', type: 'daterange', valueFormat: 'YYYY-MM-DD' },
]);
//
onMounted(() => {
initTableData();
});
//
const initTableData = () => {
speciesNameList();
reqGetUserList();
};
//
const speciesNameList = async () => {
loading.value = true;
const res = await listSpeciesName(state.tableData.param);
let list = res.data.list ?? [];
state.tableData.data = list;
versionMap.value = list.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.version;
return acc;
}, {});
state.tableData.total = res.data.total;
loading.value = false;
};
const reqGetUserList = async () => {
if (userOptions.value.length > 0) return;
const res = await getUserList({ pageSize: 9999, pageNum: 1 });
userOptions.value = res.data.userList;
};
//
const handleSelectionChange = (selection: Array<SpeciesNameInfoData>) => {
state.ids = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const showDialog = ref(false);
const formTitle = ref('');
const formFields = computed<PopupFormField[]>(() => [
{
label: '物种编码',
prop: 'speciesCode',
type: 'input',
span: 12,
rules: [{ required: true, message: '请输入物种编码', trigger: 'blur' }],
componentProps: {
placeholder: '请输入物种编码',
},
},
{
label: '物种名称',
prop: 'name',
span: 12,
type: 'input',
rules: [{ required: true, message: '请输入物种名称', trigger: 'blur' }],
componentProps: {
placeholder: '请输入物种名称',
},
},
{
label: '物种名称内容',
prop: 'content',
type: 'input',
rules: [{ required: true, message: '请输入物种名称内容', trigger: 'blur' }],
componentProps: {
placeholder: '请输入物种名称内容',
type: 'textarea',
},
},
{
label: '数据来源',
prop: 'sourcesData',
span: 24,
type: 'input',
rules: [{ required: true, message: '请输入数据来源', trigger: 'blur' }],
componentProps: {
placeholder: '请输入数据来源',
},
},
{
label: '数据采集人',
span: 12,
prop: 'createUser',
type: 'select',
rules: [{ required: true, message: '请选择数据采集人', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集人',
},
options: userOptions.value.map((i) => {
return {
label: i.userNickname,
value: i.id,
};
}),
},
{
label: '数据采集日期',
span: 12,
prop: 'createDate',
type: 'date',
rules: [{ required: true, message: '请选择数据采集日期', trigger: 'change' }],
componentProps: {
placeholder: '请选择数据采集日期',
},
},
// {
// label: '',
// span: 12,
// prop: 'auditUser',
// type: 'select',
// rules: [{ required: true, message: '', trigger: 'change' }],
// componentProps: {
// placeholder: '',
// },
// options: userOptions.value.map((i) => {
// return {
// label: i.userNickname,
// value: i.id,
// };
// }),
// },
// {
// label: '',
// span: 12,
// prop: 'auditDate',
// type: 'date',
// rules: [{ required: true, message: '', trigger: 'change' }],
// componentProps: {
// placeholder: '',
// },
// },
{
label: '备注',
prop: 'remark',
span: 24,
type: 'input',
componentProps: {
placeholder: '请输入备注',
type: 'textarea',
},
},
]);
const formData = ref<SpeciesNameInfoData>({
id: undefined,
speciesCode: undefined,
name: undefined,
content: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
auditUser: undefined,
auditDate: undefined,
remark: undefined,
});
const formType = ref<'add' | 'edit'>('add');
const handleFormSubmit = (form: SpeciesNameInfoData) => {
if (formType.value === 'add') {
addSpeciesName(form).then(() => {
ElMessage.success('新增成功');
handleCancel();
speciesNameList();
});
} else {
form.auditStatus = 0;
updateSpeciesName(form).then(() => {
ElMessage.success('编辑成功');
handleCancel();
speciesNameList();
});
}
};
const handleCancel = () => {
showDialog.value = false;
formTitle.value = '';
formType.value = 'add';
formData.value = {
id: undefined,
speciesCode: undefined,
name: undefined,
content: undefined,
sourcesData: undefined,
createUser: undefined,
createDate: undefined,
auditUser: undefined,
auditDate: undefined,
remark: undefined,
};
};
const handleAdd = () => {
showDialog.value = true;
formTitle.value = '新增物种名称';
formType.value = 'add';
};
const handleUpdate = (row: SpeciesNameTableColumns) => {
showDialog.value = true;
formTitle.value = '编辑物种名称';
formType.value = 'edit';
formData.value = cloneDeep(row);
};
const handleDelete = (row: SpeciesNameTableColumns | null) => {
let msg = '你确定要删除所选数据?';
let id: number[] = [];
if (row) {
msg = `此操作将永久删除数据,是否继续?`;
id = [row.id];
} else {
id = state.ids;
}
if (id.length === 0) {
ElMessage.error('请选择要删除的数据。');
return;
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
delSpeciesName(
id,
id.map((i) => versionMap.value[i])
).then(() => {
ElMessage.success('删除成功');
speciesNameList();
});
})
.catch(() => {});
};
const auditFormRef = ref();
const showAuditDialog = ref(false);
const handleAudit = (row: SpeciesNameTableColumns) => {
auditFormRef.value.open(cloneDeep(row), userOptions.value);
};
const onAuditSubmit = (val: SpeciesNameTableColumns) => {
auditSpeciesName(val).then(() => {
ElMessage.success('审核成功');
showAuditDialog.value = false;
speciesNameList();
});
};
</script>

View File

@ -0,0 +1,46 @@
export interface SpeciesNameTableColumns {
id: number; // 主键
speciesCode: string; // 物种编号
name: string; // 物种名称
content: string; // 物种名称内容
sourcesData: string; // 数据来源
createUser: number; // 数据采集人
createDate: string; // 数据采集信息
auditUser: number; // 数据核查人
auditDate: string; // 数据核查日期
remark: string; // 备注
}
export interface SpeciesNameInfoData {
id: number | undefined; // 主键
speciesCode: string | undefined; // 物种编号
name: string | undefined; // 物种名称
content: string | undefined; // 物种名称内容
sourcesData: string | undefined; // 数据来源
createUser: number | undefined; // 数据采集人
createDate: string | undefined; // 数据采集信息
auditUser: number | undefined; // 数据核查人
auditDate: string | undefined; // 数据核查日期
remark: string | undefined; // 备注
auditStatus?: number | undefined; // 审核状态
}
export interface SpeciesNameTableDataState {
ids: any[];
tableData: {
data: Array<SpeciesNameTableColumns>;
total: number;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
};
};
}
export interface SpeciesNameEditState {
loading: boolean;
isShowDialog: boolean;
formData: SpeciesNameInfoData;
rules: object;
}

View File

@ -1,16 +0,0 @@
import {defineComponent,h} from "vue";
export default defineComponent({
name:"baiduMap",
props:{
src:{
type:String,
default:'',
}
},
setup(prop){
return ()=>{
return h('script',{src:prop.src,type:'text/javascript'})
}
},
})

View File

@ -117,7 +117,7 @@ const state = reactive({
isShowPassword: false, isShowPassword: false,
ruleForm: { ruleForm: {
username: 'demo', username: 'demo',
password: '123456', password: 'Demo123',
verifyCode: '', verifyCode: '',
verifyKey:'' verifyKey:''
}, },

View File

@ -6,7 +6,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineComponent, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import QRCode from 'qrcodejs2-fixes'; import QRCode from 'qrcodejs2-fixes';
defineOptions({ name: "loginScan"}) defineOptions({ name: "loginScan"})
const qrcodeRef = ref<HTMLElement | null>(null); const qrcodeRef = ref<HTMLElement | null>(null);

View File

@ -14,24 +14,11 @@
<el-tab-pane :label="$t('message.label.one1')" name="account"> <el-tab-pane :label="$t('message.label.one1')" name="account">
<Account /> <Account />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile"> <!-- <el-tab-pane :label="$t('message.label.two2')" name="mobile">
<Mobile /> <Mobile />
</el-tab-pane> </el-tab-pane> -->
</el-tabs> </el-tabs>
</div> </div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
<div class="login-footer">
<div class="login-footer-content mt15">
<div class="login-footer-content-warp">
<div>Copyright © 2021-2023 g-fast.cn All Rights Reserved.</div>
<div class="mt5">云南奇讯科技有限公司版权所有</div>
</div> </div>
</div> </div>
</div> </div>
@ -39,15 +26,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted } from 'vue'; import { toRefs, reactive, computed, onMounted } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig'; import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg'; import logoMini from '/@/assets/logo-mini.svg';
import loginIconTwo from '/@/assets/login-icon-two.svg';
import { NextLoading } from '/@/utils/loading'; import { NextLoading } from '/@/utils/loading';
import Account from '/@/views/login/component/account.vue'; import Account from '/@/views/login/component/account.vue';
import Mobile from '/@/views/login/component/mobile.vue';
import Scan from '/@/views/login/component/scan.vue';
// //
interface LoginState { interface LoginState {

View File

@ -1,164 +0,0 @@
<template>
<div class="notice-bar-container">
<el-card shadow="hover" header="滚动通知栏:默认">
<NoticeBar
text="🎉🎉🔥基于vue3.x TypescriptviteElement plus等适配手机平板pc
的后台开源免费模板库vue2.x请切换vue-prev-admin分支仓库地址https://gitee.com/lyt-top/vue-next-admin"
/>
</el-card>
<el-card shadow="hover" header="滚动通知栏:设置样式" class="mt15">
<NoticeBar
text="🎉🎉🔥基于vue3.x TypescriptviteElement plus等适配手机平板pc
的后台开源免费模板库vue2.x请切换vue-prev-admin分支仓库地址https://gitee.com/lyt-top/vue-next-admin"
leftIcon="iconfont icon-tongzhi2"
rightIcon="ele-ArrowRight"
background="#ecf5ff"
color="#409eff"
/>
</el-card>
<el-card shadow="hover" header="滚动通知栏:搭配 NoticeBar 和 Carousel 走马灯 组件可以实现垂直滚动的效果" class="mt15">
<NoticeBar :scrollable="true">
<el-carousel height="40px" direction="vertical" :autoplay="true" indicator-position="none" :interval="3000">
<el-carousel-item v-for="v in noticeList" :key="v">{{ v }} </el-carousel-item>
</el-carousel>
</NoticeBar>
</el-card>
<el-card shadow="hover" header="滚动通知栏:参数" class="mt15">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="a1" label="参数"> </el-table-column>
<el-table-column prop="a2" label="说明"> </el-table-column>
<el-table-column prop="a3" label="类型"> </el-table-column>
<el-table-column prop="a4" label="可选值"> </el-table-column>
<el-table-column prop="a5" label="默认值"> </el-table-column>
</el-table>
</el-card>
<el-card shadow="hover" header="图标选择器(宽度自动):事件" class="mt15">
<el-table :data="tableData1" style="width: 100%">
<el-table-column prop="a1" label="事件名称"> </el-table-column>
<el-table-column prop="a2" label="说明"> </el-table-column>
<el-table-column prop="a3" label="类型"> </el-table-column>
<el-table-column prop="a4" label="回调参数"> </el-table-column>
</el-table>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import NoticeBar from '/@/components/noticeBar/index.vue';
export default defineComponent({
name: 'makeNoticeBar',
components: { NoticeBar },
setup() {
const state = reactive({
noticeList: [
'🎉🎉🔥基于vue3.x 、Typescript、vite、Element plus等',
'适配手机、平板、pc的后台开源免费模板库vue2.x请切换vue-prev-admin分支',
'仓库地址https://gitee.com/lyt-top/vue-next-admin',
'演示地址https://lyt-top.gitee.io/vue-next-admin-preview/#/login',
],
tableData: [
{
a1: 'mode',
a2: '通知栏模式,用于右侧 icon 图标点击',
a3: 'string',
a4: 'closeable / link',
a5: '',
},
{
a1: 'text',
a2: '通知文本内容scrollable 为 false 时生效',
a3: 'string',
a4: '',
a5: '',
},
{
a1: 'color',
a2: '通知文本颜色',
a3: 'string',
a4: '',
a5: '#e6a23c',
},
{
a1: 'background',
a2: '通知背景色',
a3: 'string',
a4: '',
a5: '#fdf6ec',
},
{
a1: 'size',
a2: '字体大小单位px',
a3: 'number / string',
a4: '',
a5: '14',
},
{
a1: 'height',
a2: '通知栏高度单位px',
a3: 'number / string',
a4: '',
a5: '40',
},
{
a1: 'delay',
a2: '动画延迟时间 (s)',
a3: 'number / string',
a4: '',
a5: '1',
},
{
a1: 'speed',
a2: '滚动速率 (px/s)',
a3: 'number / string',
a4: '',
a5: '100',
},
{
a1: 'scrollable',
a2: '是否开启垂直滚动',
a3: 'boolean',
a4: 'true',
a5: 'false',
},
{
a1: 'leftIcon',
a2: '自定义左侧图标',
a3: 'string',
a4: '',
a5: '',
},
{
a1: 'rightIcon',
a2: '自定义右侧图标',
a3: 'string',
a4: '',
a5: '',
},
],
tableData1: [
{
a1: 'close',
a2: '通知栏模式modecloseable 时回调事件',
a3: 'function',
a4: '',
},
{
a1: 'link',
a2: '通知栏模式modelink 时回调事件',
a3: 'function',
a4: '',
},
],
});
return {
...toRefs(state),
};
},
});
</script>

View File

@ -1,126 +0,0 @@
<template>
<div class="selector-container">
<el-card shadow="hover" header="图标选择器(宽度自动)">
<IconSelector @get="onGetIcon" @clear="onClearIcon" v-model="modelIcon" />
</el-card>
<el-card shadow="hover" header="图标选择器(宽度自动):参数" class="mt15">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="a1" label="参数"> </el-table-column>
<el-table-column prop="a2" label="说明"> </el-table-column>
<el-table-column prop="a3" label="类型"> </el-table-column>
<el-table-column prop="a4" label="可选值"> </el-table-column>
<el-table-column prop="a5" label="默认值"> </el-table-column>
</el-table>
</el-card>
<el-card shadow="hover" header="图标选择器(宽度自动):事件" class="mt15">
<el-table :data="tableData1" style="width: 100%">
<el-table-column prop="a1" label="事件名称"> </el-table-column>
<el-table-column prop="a2" label="说明"> </el-table-column>
<el-table-column prop="a3" label="类型"> </el-table-column>
<el-table-column prop="a4" label="回调参数"> </el-table-column>
</el-table>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import IconSelector from '/@/components/iconSelector/index.vue';
export default defineComponent({
name: 'makeSelector',
components: { IconSelector },
setup() {
const state = reactive({
modelIcon: '',
tableData: [
{
a1: 'prepend',
a2: '输入框前置内容,只能字体图标',
a3: 'string',
a4: '',
a5: 'ele-Pointer',
},
{
a1: 'placeholder',
a2: '输入框占位文本',
a3: 'string',
a4: '',
a5: '请输入内容搜索图标或者选择图标',
},
{
a1: 'size',
a2: '尺寸',
a3: 'string',
a4: 'large / default / small',
a5: 'default',
},
{
a1: 'title',
a2: '弹窗标题',
a3: 'string',
a4: '',
a5: '请选择图标',
},
{
a1: 'type',
a2: 'icon 图标类型',
a3: 'string',
a4: 'ali / ele / awe / all',
a5: 'ele',
},
{
a1: 'disabled',
a2: '禁用',
a3: 'boolean',
a4: 'true',
a5: 'false',
},
{
a1: 'clearable',
a2: '是否可清空',
a3: 'boolean',
a4: 'false',
a5: 'true',
},
{
a1: 'emptyDescription',
a2: '自定义空状态描述文字',
a3: 'String',
a4: '',
a5: '无相关图标',
},
],
tableData1: [
{
a1: 'get',
a2: '获取当前点击的 icon 图标',
a3: 'function',
a4: '(icon: string)',
},
{
a1: 'clear',
a2: '清空当前点击的 icon 图标',
a3: 'function',
a4: '(icon: string)',
},
],
});
// icon
const onGetIcon = (icon: string) => {
console.log(icon);
};
// icon
const onClearIcon = (icon: string) => {
console.log(icon);
};
return {
onGetIcon,
onClearIcon,
...toRefs(state),
};
},
});
</script>

View File

@ -1,59 +0,0 @@
<template>
<div class="svg-demo-container">
<el-card shadow="hover" header="svgIcon演示支持本地svg">
<SvgIcon name="iconfont icon-shuju1" color="red" :size="30" />
<SvgIcon name="ele-Trophy" color="var(--el-color-primary)" :size="30" />
<SvgIcon name="fa fa-flag-checkered" color="#09f" :size="30" />
<SvgIcon :name="logoMini" color="#09f" :size="30" />
</el-card>
<el-card shadow="hover" header="svgIcon参数" class="mt15">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="a1" label="参数"> </el-table-column>
<el-table-column prop="a2" label="说明"> </el-table-column>
<el-table-column prop="a3" label="类型"> </el-table-column>
<el-table-column prop="a4" label="可选值"> </el-table-column>
<el-table-column prop="a5" label="默认值"> </el-table-column>
</el-table>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import logoMini from '/@/assets/logo-mini.svg';
export default defineComponent({
name: 'makeSvgDemo',
setup() {
const state = reactive({
tableData: [
{
a1: 'name',
a2: 'svg 图标组件名字 / svg 路径 url',
a3: 'string',
a4: '',
a5: '',
},
{
a1: 'size',
a2: 'svg 大小',
a3: 'number',
a4: '',
a5: 14,
},
{
a1: 'color',
a2: 'svg 颜色',
a3: 'string',
a4: '',
a5: '',
},
],
});
return {
logoMini,
...toRefs(state),
};
},
});
</script>

View File

@ -1,127 +0,0 @@
<template>
<div>
<el-card>
<el-alert type="error" title="消息队列测试先生产一条消息再订阅先订阅不存在的topic会失败" :closable="false"></el-alert>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="10">
<div class="my-title">生产者</div>
<el-form :label-width="80">
<el-form-item label="topic">
<el-input v-model="demoForm.topic" />
</el-form-item>
<el-form-item label="topic">
<el-input v-model="demoForm.body" type="textarea" :rows="6" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="default" @click="handleProduce">生产消息</el-button>
</el-form-item>
</el-form>
</el-col>
<el-col :span="14">
<div class="my-title">消费者</div>
<el-form :label-width="80">
<el-form-item label="topic">
<el-input v-model="demoForm.topic" :disabled="wsReady" />
</el-form-item>
<el-form-item label="channel">
<el-input v-model="demoForm.channel" :disabled="wsReady" />
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="handleStartSubscribe" :disabled="wsReady">订阅消费</el-button>
<el-button size="default" type="primary" @click="handleUnSubscribe" :disabled="!wsReady">取消订阅</el-button>
</el-form-item>
<el-form-item label="消费结果">
<el-input :model-value="messageResult" type="textarea" :rows="12"></el-input>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup lang="ts">
import {reactive, ref} from "vue";
import {ElMessageBox} from "element-plus";
import request from "/@/utils/request";
const demoForm = reactive({
topic: "producer_topic_test",
body: "测试消息队列内容",
channel: "channel1"
})
const consumerForm = reactive({
topic:"producer_topic_test",
channel:"channel1"
})
function handleProduce(){
request({
url: '/api/v1/mqueue/demo/produce',
method: 'post',
data: {topic:demoForm.topic,body:demoForm.body}
}).then((res:any)=>{
window.console.log(res)
})
}
const webSocketURL = import.meta.env.VITE_WEBSOCKET_URL
const messageResult = ref("")
let ws:WebSocket|null = null
const wsReady = ref(false)
function handleStartSubscribe(){
if(ws) return
const url = webSocketURL+"api/v1/mqueue/demo/subscribe?topic="+demoForm.topic+"&channel="+demoForm.channel;
ws = new WebSocket(url);
try {
// ws
ws.onopen = function () {
messageResult.value += "WebSocket Server [" + url +"] 连接成功!\r\n";
wsReady.value = true
};
// ws
ws.onclose = function () {
if (ws) {
ws.close();
ws = null;
wsReady.value = false
}
messageResult.value += "WebSocket Server [" + url +"] 连接被服务器关闭!\r\n";
};
// ws
ws.onerror = function () {
if (ws) {
ws.close();
ws = null;
wsReady.value = false
}
messageResult.value += "WebSocket Server [" + url +"] 连接错误!\r\n";
};
// ws
ws.onmessage = function (result) {
window.console.log(result)
messageResult.value += " > " + result.data+"\r\n";
};
} catch (e:any) {
ElMessageBox.alert(e.message)
}
}
function handleUnSubscribe(){
if(ws) {
ws.close()
ws = null;
wsReady.value = false
messageResult.value += "已手动取消订阅!\r\n";
}
}
</script>
<style scoped lang="scss">
.my-title{
font-size: 16px; font-weight: bold;line-height: 2;
color: #1890ff;
text-align: center;
}
</style>

View File

@ -1,387 +0,0 @@
<template>
<div class="personal">
<el-row>
<!-- 个人信息 -->
<el-col :xs="24" :sm="16">
<el-card shadow="hover" header="个人信息">
<div class="personal-user">
<div class="personal-user-left">
<el-upload class="h100 personal-user-left-upload" action="https://jsonplaceholder.typicode.com/posts/" multiple :limit="1">
<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
</el-upload>
</div>
<div class="personal-user-right">
<el-row>
<el-col :span="24" class="personal-title mb18">{{ currentTime }}admin生活变的再糟糕也不妨碍我变得更好 </el-col>
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="8" class="personal-item mb6">
<div class="personal-item-label">昵称</div>
<div class="personal-item-value">小柒</div>
</el-col>
<el-col :xs="24" :sm="16" class="personal-item mb6">
<div class="personal-item-label">身份</div>
<div class="personal-item-value">超级管理</div>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="8" class="personal-item mb6">
<div class="personal-item-label">登录IP</div>
<div class="personal-item-value">192.168.1.1</div>
</el-col>
<el-col :xs="24" :sm="16" class="personal-item mb6">
<div class="personal-item-label">登录时间</div>
<div class="personal-item-value">2021-02-05 18:47:26</div>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</div>
</el-card>
</el-col>
<!-- 消息通知 -->
<el-col :xs="24" :sm="8" class="pl15 personal-info">
<el-card shadow="hover">
<template #header>
<span>消息通知</span>
<span class="personal-info-more">更多</span>
</template>
<div class="personal-info-box">
<ul class="personal-info-ul">
<li v-for="(v, k) in newsInfoList" :key="k" class="personal-info-li">
<a :href="v.link" target="_block" class="personal-info-li-title">{{ v.title }}</a>
</li>
</ul>
</div>
</el-card>
</el-col>
<!-- 营销推荐 -->
<el-col :span="24">
<el-card shadow="hover" class="mt15" header="营销推荐">
<el-row :gutter="15" class="personal-recommend-row">
<el-col :sm="6" v-for="(v, k) in recommendList" :key="k" class="personal-recommend-col">
<div class="personal-recommend" :style="{ 'background-color': v.bg }">
<SvgIcon :name="v.icon" :size="70" :style="{ color: v.iconColor }" />
<div class="personal-recommend-auto">
<div>{{ v.title }}</div>
<div class="personal-recommend-msg">{{ v.msg }}</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
<!-- 更新信息 -->
<el-col :span="24">
<el-card shadow="hover" class="mt15 personal-edit" header="更新信息">
<div class="personal-edit-title">基本信息</div>
<el-form :model="personalForm" size="default" label-width="40px" class="mt35 mb35">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="昵称">
<el-input v-model="personalForm.name" placeholder="请输入昵称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="personalForm.email" placeholder="请输入邮箱" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="签名">
<el-input v-model="personalForm.autograph" placeholder="请输入签名" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="职业">
<el-select v-model="personalForm.occupation" placeholder="请选择职业" clearable class="w100">
<el-option label="计算机 / 互联网 / 通信" value="1"></el-option>
<el-option label="生产 / 工艺 / 制造" value="2"></el-option>
<el-option label="医疗 / 护理 / 制药" value="3"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="手机">
<el-input v-model="personalForm.phone" placeholder="请输入手机" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="性别">
<el-select v-model="personalForm.sex" placeholder="请选择性别" clearable class="w100">
<el-option label="男" value="1"></el-option>
<el-option label="女" value="2"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item>
<el-button type="primary">
<el-icon>
<ele-Position />
</el-icon>
更新个人信息
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="personal-edit-title mb15">账号安全</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">账户密码</div>
<div class="personal-edit-safe-item-left-value">当前密码强度</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即修改</el-button>
</div>
</div>
</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">密保手机</div>
<div class="personal-edit-safe-item-left-value">已绑定手机132****4108</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即修改</el-button>
</div>
</div>
</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">密保问题</div>
<div class="personal-edit-safe-item-left-value">已设置密保问题账号安全大幅度提升</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即设置</el-button>
</div>
</div>
</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">绑定QQ</div>
<div class="personal-edit-safe-item-left-value">已绑定QQ110****566</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即设置</el-button>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent } from 'vue';
import { formatAxis } from '/@/utils/formatTime';
import { newsInfoList, recommendList } from './mock';
//
interface PersonalState {
newsInfoList: any;
recommendList: any;
personalForm: any;
}
export default defineComponent({
name: 'personal',
setup() {
const state = reactive<PersonalState>({
newsInfoList,
recommendList,
personalForm: {
name: '',
email: '',
autograph: '',
occupation: '',
phone: '',
sex: '',
},
});
//
const currentTime = computed(() => {
return formatAxis(new Date());
});
return {
currentTime,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
@use '../../theme/mixins/index.scss' as *;
.personal {
.personal-user {
height: 130px;
display: flex;
align-items: center;
.personal-user-left {
width: 100px;
height: 130px;
border-radius: 3px;
:deep(.el-upload) {
height: 100%;
}
.personal-user-left-upload {
img {
width: 100%;
height: 100%;
border-radius: 3px;
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;
}
}
}
}
.personal-user-right {
flex: 1;
padding: 0 15px;
.personal-title {
font-size: 18px;
@include text-ellipsis(1);
}
.personal-item {
display: flex;
align-items: center;
font-size: 13px;
.personal-item-label {
color: var(--el-text-color-secondary);
@include text-ellipsis(1);
}
.personal-item-value {
@include text-ellipsis(1);
}
}
}
}
.personal-info {
.personal-info-more {
float: right;
color: var(--el-text-color-secondary);
font-size: 13px;
&:hover {
color: var(--el-color-primary);
cursor: pointer;
}
}
.personal-info-box {
height: 130px;
overflow: hidden;
.personal-info-ul {
list-style: none;
.personal-info-li {
font-size: 13px;
padding-bottom: 10px;
.personal-info-li-title {
display: inline-block;
@include text-ellipsis(1);
color: var(--el-text-color-secondary);
text-decoration: none;
}
& a:hover {
color: var(--el-color-primary);
cursor: pointer;
}
}
}
}
}
.personal-recommend-row {
.personal-recommend-col {
.personal-recommend {
position: relative;
height: 100px;
border-radius: 3px;
overflow: hidden;
cursor: pointer;
&:hover {
i {
right: 0px !important;
bottom: 0px !important;
transition: all ease 0.3s;
}
}
i {
position: absolute;
right: -10px;
bottom: -10px;
font-size: 70px;
transform: rotate(-30deg);
transition: all ease 0.3s;
}
.personal-recommend-auto {
padding: 15px;
position: absolute;
left: 0;
top: 5%;
color: var(--next-color-white);
.personal-recommend-msg {
font-size: 12px;
margin-top: 10px;
}
}
}
}
}
.personal-edit {
.personal-edit-title {
position: relative;
padding-left: 10px;
color: var(--el-text-color-regular);
&::after {
content: '';
width: 2px;
height: 10px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
background: var(--el-color-primary);
}
}
.personal-edit-safe-box {
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
padding: 15px 0;
.personal-edit-safe-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.personal-edit-safe-item-left {
flex: 1;
overflow: hidden;
.personal-edit-safe-item-left-label {
color: var(--el-text-color-regular);
margin-bottom: 5px;
}
.personal-edit-safe-item-left-value {
color: var(--el-text-color-secondary);
@include text-ellipsis(1);
margin-right: 15px;
}
}
}
&:last-of-type {
padding-bottom: 0;
border-bottom: none;
}
}
}
}
</style>

View File

@ -1,66 +0,0 @@
/**
*
* @returns
*/
export const newsInfoList: Array<object> = [
{
title: '[发布] 2021年02月28日发布基于 vue3.x + vite v1.0.0 版本',
date: '02/28',
link: 'https://gitee.com/lyt-top/vue-next-admin',
},
{
title: '[发布] 2021年04月15日发布 vue2.x + webpack 重构版本',
date: '04/15',
link: 'https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin/',
},
{
title: '[重构] 2021年04月10日 重构 vue2.x + webpack v1.0.0 版本',
date: '04/10',
link: 'https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin/',
},
{
title: '[预览] 2020年12月08日基于 vue3.x 版本后台模板的预览',
date: '12/08',
link: 'http://lyt-top.gitee.io/vue-next-admin-preview/#/login',
},
{
title: '[预览] 2020年11月15日基于 vue2.x 版本后台模板的预览',
date: '11/15',
link: 'https://lyt-top.gitee.io/vue-prev-admin-preview/#/login',
},
];
/**
*
* @returns
*/
export const recommendList: Array<object> = [
{
title: '优惠券',
msg: '现金券、折扣券、营销必备',
icon: 'ele-Food',
bg: '#48D18D',
iconColor: '#64d89d',
},
{
title: '多人拼团',
msg: '社交电商、开辟流量',
icon: 'ele-ShoppingCart',
bg: '#F95959',
iconColor: '#F86C6B',
},
{
title: '分销中心',
msg: '轻松招募分销员,成功推广奖励',
icon: 'ele-School',
bg: '#8595F4',
iconColor: '#92A1F4',
},
{
title: '秒杀',
msg: '超低价抢购引导更多销量',
icon: 'ele-AlarmClock',
bg: '#FEBB50',
iconColor: '#FDC566',
},
];

View File

@ -2,6 +2,9 @@
<div class="system-edit-dic-container"> <div class="system-edit-dic-container">
<el-dialog :title="(ruleForm.dictId!==0?'修改':'添加')+'字典'" v-model="isShowDialog" width="769px"> <el-dialog :title="(ruleForm.dictId!==0?'修改':'添加')+'字典'" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="90px"> <el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="90px">
<el-form-item label="上级" prop="pid">
<el-cascader v-model="ruleForm.pid" :options="dictTypeOpt" :props="typeProps" clearable />
</el-form-item>
<el-form-item label="字典名称" prop="dictName"> <el-form-item label="字典名称" prop="dictName">
<el-input v-model="ruleForm.dictName" placeholder="请输入字典名称" /> <el-input v-model="ruleForm.dictName" placeholder="请输入字典名称" />
</el-form-item> </el-form-item>
@ -29,11 +32,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs, defineComponent,ref, unref } from 'vue'; import { reactive, toRefs, ref, unref, getCurrentInstance } from 'vue';
import { getType,addType,editType } from '/@/api/system/dict/type'; import { getType, addType, editType, optionselect } from '/@/api/system/dict/type';
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
interface RuleFormState { interface RuleFormState {
dictId:number; dictId:number;
pid:number;
dictName:string; dictName:string;
dictType:string; dictType:string;
status:number; status:number;
@ -45,12 +49,22 @@ interface DicState {
rules:{} rules:{}
} }
defineOptions({ name: "systemEditDic"}) defineOptions({ name: "systemEditDic"})
const {proxy} = getCurrentInstance() as any;
const emit = defineEmits(['typeList']); const emit = defineEmits(['typeList']);
const formRef = ref<HTMLElement | null>(null); const formRef = ref<HTMLElement | null>(null);
const dictTypeOpt = ref<RuleFormState>()
const typeProps = ref({
value: 'dictId',
label: 'dictName',
children: 'children',
checkStrictly:true,
emitPath: false
})
const state = reactive<DicState>({ const state = reactive<DicState>({
isShowDialog: false, isShowDialog: false,
ruleForm: { ruleForm: {
dictId:0, dictId:0,
pid:0,
dictName:'', dictName:'',
dictType:'', dictType:'',
status:1, status:1,
@ -66,9 +80,16 @@ const state = reactive<DicState>({
} }
}); });
const { isShowDialog,ruleForm,rules } = toRefs(state); const { isShowDialog,ruleForm,rules } = toRefs(state);
const getParent = () => {
optionselect(true).then((res:any)=>{
const data = res.data.dictType??[]
dictTypeOpt.value = proxy.handleTree(data, 'dictId', 'pid', 'children', true)
})
}
// //
const openDialog = (row: RuleFormState|null) => { const openDialog = (row: RuleFormState|null) => {
resetForm(); resetForm();
getParent();
if (row){ if (row){
getType(row.dictId).then((res:any)=>{ getType(row.dictId).then((res:any)=>{
state.ruleForm = res.data.dictType state.ruleForm = res.data.dictType
@ -81,6 +102,7 @@ defineExpose({ openDialog})
const resetForm = ()=>{ const resetForm = ()=>{
state.ruleForm = { state.ruleForm = {
dictId:0, dictId:0,
pid:0,
dictName:'', dictName:'',
dictType:'', dictType:'',
status:1, status:1,

View File

@ -2,8 +2,8 @@
<div class="system-edit-dic-container"> <div class="system-edit-dic-container">
<el-dialog :title="(ruleForm.dictCode!==0?'修改':'添加')+'字典'" v-model="isShowDialog" width="769px"> <el-dialog :title="(ruleForm.dictCode!==0?'修改':'添加')+'字典'" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="90px"> <el-form :model="ruleForm" ref="formRef" :rules="rules" size="default" label-width="90px">
<el-form-item label="字典类型"> <el-form-item label="字典类型" prop="dictType">
<el-input v-model="ruleForm.dictType" :disabled="true" /> <el-cascader v-model="ruleForm.dictType" :options="dictTypeOpt" :props="typeProps" clearable :show-all-levels="false"/>
</el-form-item> </el-form-item>
<el-form-item label="数据标签" prop="dictLabel"> <el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="ruleForm.dictLabel" placeholder="请输入数据标签" /> <el-input v-model="ruleForm.dictLabel" placeholder="请输入数据标签" />
@ -45,9 +45,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs, defineComponent,ref, unref } from 'vue'; import { reactive, toRefs, ref, unref, getCurrentInstance } from 'vue';
import { getData,addData,editData } from '/@/api/system/dict/data'; import { getData,addData,editData } from '/@/api/system/dict/data';
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import { optionselect } from '/@/api/system/dict/type';
interface RuleFormState { interface RuleFormState {
dictCode: number; dictCode: number;
dictLabel: string; dictLabel: string;
@ -64,6 +65,7 @@ interface DicState {
rules:{} rules:{}
} }
defineOptions({ name: "systemEditDicData"}) defineOptions({ name: "systemEditDicData"})
const {proxy} = getCurrentInstance() as any;
const prop = defineProps({ const prop = defineProps({
dictType:{ dictType:{
type:String, type:String,
@ -72,6 +74,14 @@ const prop = defineProps({
}) })
const emit = defineEmits(['dataList']); const emit = defineEmits(['dataList']);
const formRef = ref<HTMLElement | null>(null); const formRef = ref<HTMLElement | null>(null);
const dictTypeOpt = ref<RuleFormState>()
const typeProps = ref({
value: 'dictType',
label: 'dictName',
children: 'children',
emitPath: false,
checkStrictly:true
})
const state = reactive<DicState>({ const state = reactive<DicState>({
isShowDialog: false, isShowDialog: false,
ruleForm: { ruleForm: {
@ -85,6 +95,9 @@ const state = reactive<DicState>({
dictType:prop.dictType dictType:prop.dictType
}, },
rules: { rules: {
dictType:[
{ required: true, message: "请选择字典类型", trigger: "change" }
],
dictLabel: [ dictLabel: [
{ required: true, message: "数据标签不能为空", trigger: "blur" } { required: true, message: "数据标签不能为空", trigger: "blur" }
], ],
@ -97,9 +110,16 @@ const state = reactive<DicState>({
} }
}); });
const { isShowDialog,ruleForm,rules } = toRefs(state); const { isShowDialog,ruleForm,rules } = toRefs(state);
const getTypeOpt = () => {
optionselect(true).then((res:any)=>{
const data = res.data.dictType??[]
dictTypeOpt.value = proxy.handleTree(data, 'dictId', 'pid', 'children', true)
})
}
// //
const openDialog = (row: RuleFormState|null) => { const openDialog = (row: RuleFormState|null) => {
resetForm(); getTypeOpt()
resetForm()
if (row){ if (row){
getData(row.dictCode).then((res:any)=>{ getData(row.dictCode).then((res:any)=>{
state.ruleForm = res.data.dict state.ruleForm = res.data.dict

View File

@ -64,7 +64,7 @@
<el-table-column label="字典标签" align="center" prop="dictLabel" /> <el-table-column label="字典标签" align="center" prop="dictLabel" />
<el-table-column label="字典键值" align="center" prop="dictValue" /> <el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" /> <el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> <el-table-column label="字典类型" align="center" prop="dictType" />
<el-table-column label="创建时间" align="center" prop="createdAt" width="180"/> <el-table-column label="创建时间" align="center" prop="createdAt" width="180"/>
<el-table-column prop="status" label="字典状态" show-overflow-tooltip> <el-table-column prop="status" label="字典状态" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
@ -87,12 +87,12 @@
@pagination="dataList" @pagination="dataList"
/> />
</el-card> </el-card>
<EditDic ref="editDicRef" @dataList="dataList" :dict-type="tableData.param.dictType"/> <EditDic ref="editDicRef" @dataList="dataList" :dict-type="selectedTypeCode"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue'; import { toRefs, reactive, onMounted, ref } from 'vue';
import { ElMessageBox, ElMessage,FormInstance} from 'element-plus'; import { ElMessageBox, ElMessage,FormInstance} from 'element-plus';
import EditDic from '/@/views/system/dict/component/editDicData.vue'; import EditDic from '/@/views/system/dict/component/editDicData.vue';
import {getDataList,deleteData} from "/@/api/system/dict/data"; import {getDataList,deleteData} from "/@/api/system/dict/data";
@ -121,14 +121,15 @@ interface TableDataState {
dictType: string; dictType: string;
dictLabel:string; dictLabel:string;
status: string; status: string;
typeId:number;
}; };
}; };
} }
defineOptions({ name: "apiV1SystemDictDataList"}) defineOptions({ name: "apiV1SystemDictDataList"})
const route = useRoute(); const route = useRoute();
const addDicRef = ref();
const editDicRef = ref(); const editDicRef = ref();
const queryRef = ref(); const queryRef = ref();
const selectedTypeCode = ref("");
const state = reactive<TableDataState>({ const state = reactive<TableDataState>({
ids:[], ids:[],
tableData: { tableData: {
@ -140,14 +141,17 @@ const state = reactive<TableDataState>({
pageSize: 10, pageSize: 10,
dictLabel:'', dictLabel:'',
dictType:'', dictType:'',
status:'' status:'',
typeId:0
}, },
}, },
}); });
const { tableData } = toRefs(state); const { tableData } = toRefs(state);
// //
const initTableData = () => { const initTableData = (typeId?: number,code?:string) => {
dataList() state.tableData.param.typeId = typeId as number;
selectedTypeCode.value = code as string;
dataList()
}; };
const dataList=()=>{ const dataList=()=>{
getDataList(state.tableData.param).then((res:any)=>{ getDataList(state.tableData.param).then((res:any)=>{
@ -206,4 +210,5 @@ const resetQuery = (formEl: FormInstance | undefined) => {
const handleSelectionChange = (selection:TableDataRow[])=> { const handleSelectionChange = (selection:TableDataRow[])=> {
state.ids = selection.map(item => item.dictCode) state.ids = selection.map(item => item.dictCode)
}; };
defineExpose({initTableData})
</script> </script>

View File

@ -1,225 +1,191 @@
<template> <template>
<div class="system-dic-container"> <div class="system-dic-container">
<el-card shadow="hover"> <el-row :gutter="10" style="width: 100%;">
<div class="system-user-search mb15"> <el-col :span="5">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="68px"> <el-card shadow="hover">
<el-form-item label="字典名称" prop="dictName"> <el-aside style="width: 100%;">
<el-input <el-scrollbar>
v-model="tableData.param.dictName" <div class="act-btn">
placeholder="请输入字典名称" <el-button size="default" type="success" class="ml10 btm8" @click="onOpenAddDic">
clearable <el-icon>
style="width: 240px" <ele-FolderAdd />
@keyup.enter.native="typeList" </el-icon>
/> 新增
</el-form-item> </el-button>
<el-form-item label="字典类型" prop="dictType"> <el-button size="default" type="primary" class="ml10 btm8" @click="onOpenEditDic" :disabled="selectedNode===null">
<el-input <el-icon>
v-model="tableData.param.dictType" <ele-Edit />
placeholder="请输入字典类型" </el-icon>
clearable 编辑
style="width: 240px" </el-button>
@keyup.enter.native="typeList" <el-button size="default" type="danger" class="ml10 btm8" @click="onRowDel(null)" :disabled="checkedNodes.length===0">
/> <el-icon>
</el-form-item> <ele-Delete />
<el-form-item label="状态" prop="status" style="width: 200px;"> </el-icon>
<el-select 删除
v-model="tableData.param.status" </el-button>
placeholder="字典状态" <el-button size="default" type="warning" class="ml10 btm8" @click="expandedOn">
clearable <el-icon>
style="width: 240px" <ele-DCaret />
> </el-icon>
<el-option label="启用" :value="1"/> {{expanded?'收起':'展开'}}
<el-option label="禁用" :value="0"/> </el-button>
</el-select> </div>
</el-form-item> <el-divider />
<el-form-item label="创建时间" prop="dateRange"> <el-input :prefix-icon="search" v-model="filterText" placeholder="请输入字典名称" clearable style="margin-bottom: 8px;"/>
<el-date-picker <el-tree
v-model="tableData.param.dateRange" ref="treeRef"
style="width: 240px" class="filter-tree"
value-format="YYYY-MM-DD" :data="dictTypeData"
type="daterange" :props="treeProps"
range-separator="-" :filter-node-method="filterNode"
start-placeholder="开始日期" @node-click="handleNodeClick"
end-placeholder="结束日期" show-checkbox
></el-date-picker> @check-change="handleCheckChange"
</el-form-item> node-key="dictId"
<el-form-item> :check-on-click-leaf="false"
<el-button size="default" type="primary" class="ml10" @click="typeList"> />
<el-icon> </el-scrollbar>
<ele-Search /> </el-aside>
</el-icon> </el-card>
查询 </el-col>
</el-button> <el-col :span="19">
<el-button size="default" @click="resetQuery(queryRef)"> <apiV1SystemDictDataList ref="dataViewRef"></apiV1SystemDictDataList>
<el-icon> </el-col>
<ele-Refresh /> </el-row>
</el-icon> <EditDic ref="editDicRef" @typeList="getTypeData"/>
重置
</el-button>
<el-button size="default" type="success" class="ml10" @click="onOpenAddDic">
<el-icon>
<ele-FolderAdd />
</el-icon>
新增字典
</el-button>
<el-button size="default" type="danger" class="ml10" @click="onRowDel(null)">
<el-icon>
<ele-Delete />
</el-icon>
删除字典
</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典ID" align="center" prop="dictId" width="120"/>
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<router-link :to="'/system/dict/data/list/' + scope.row.dictType" class="link-type">
<span>{{ scope.row.dictType }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column prop="status" label="字典状态" show-overflow-tooltip>
<template #default="scope">
<el-tag type="success" v-if="scope.row.status">启用</el-tag>
<el-tag type="info" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="字典描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip width="180"></el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEditDic(scope.row)">修改</el-button>
<el-button size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total>0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="typeList"
/>
</el-card>
<EditDic ref="editDicRef" @typeList="typeList"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue'; import { onMounted, ref, getCurrentInstance, watch } from 'vue';
import { ElMessageBox, ElMessage,FormInstance} from 'element-plus'; import { ElMessageBox, ElMessage, ElTree } from 'element-plus';
import EditDic from '/@/views/system/dict/component/editDic.vue'; import EditDic from '/@/views/system/dict/component/editDic.vue';
import {deleteType, getTypeList} from "/@/api/system/dict/type"; import { deleteType, optionselect } from '/@/api/system/dict/type';
import { Search } from '@element-plus/icons-vue'
import apiV1SystemDictDataList from '/@/views/system/dict/dataList.vue'
// //
interface TableDataRow { interface TableDataRow {
dictId:number; dictId:number;
dictName: string; pid:number;
dictType: string; dictName: string;
status: number; dictType: string;
remark:string; status: number;
createdAt:string; remark:string;
createdAt:string;
} }
interface TableDataState { defineOptions({ name: "apiV1SystemDictTypeList"})
ids:number[]; const search = Search
tableData: { const filterText = ref('');
data: Array<TableDataRow>; const treeRef = ref()
total: number; const {proxy} = getCurrentInstance() as any;
loading: boolean;
param: {
pageNum: number;
pageSize: number;
dictName: string;
dictType: string;
status: string;
dateRange:string[];
};
};
}
defineOptions({ name: "systemDic"})
const addDicRef = ref();
const editDicRef = ref(); const editDicRef = ref();
const queryRef = ref(); const dictTypeData = ref([])
const state = reactive<TableDataState>({ const selectedNode = ref<TableDataRow|null>(null)
ids:[], const checkedNodes = ref<TableDataRow[]>([])
tableData: { const expanded = ref<boolean>(false)
data: [], const dataViewRef = ref()
total: 0, const treeProps = ref({
loading: false, value: 'dictId',
param: { label: 'dictName',
pageNum: 1, children: 'children',
pageSize: 10, checkStrictly:true,
dictName:'', emitPath: false
dictType:'', })
status:'', const getTypeData = () => {
dateRange:[], optionselect(true).then((res:any)=>{
}, const data = res.data.dictType??[]
}, dictTypeData.value = proxy.handleTree(data, 'dictId', 'pid', 'children', true)
}); })
const { tableData } = toRefs(state); }
// //
const initTableData = () => { const initTableData = () => {
typeList() getTypeData()
};
const typeList=()=>{
getTypeList(state.tableData.param).then((res:any)=>{
state.tableData.data = res.data.dictTypeList;
state.tableData.total = res.data.total;
});
}; };
// //
const onOpenAddDic = () => { const onOpenAddDic = () => {
editDicRef.value.openDialog(); editDicRef.value.openDialog();
}; };
// //
const onOpenEditDic = (row: TableDataRow) => { const onOpenEditDic = () => {
editDicRef.value.openDialog(row); const row = selectedNode.value;
editDicRef.value.openDialog(row);
}; };
// //
const onRowDel = (row: TableDataRow|null) => { const onRowDel = (row: TableDataRow|null) => {
let msg = '你确定要删除所选数据?'; let msg = '你确定要删除所选数据?';
let ids:number[] = [] ; let ids:number[] = [] ;
if(row){ if(row){
msg = `此操作将永久删除用户:“${row.dictName}”,是否继续?` msg = `此操作将永久删除用户:“${row.dictName}”,是否继续?`
ids = [row.dictId] ids = [row.dictId]
}else{ }else{
ids = state.ids checkedNodes.value.forEach(item=>{
} ids.push(item.dictId);
if(ids.length===0){ })
ElMessage.error('请选择要删除的数据。'); }
return if(ids.length===0){
} ElMessage.error('请选择要删除的数据。');
ElMessageBox.confirm(msg, '提示', { return
confirmButtonText: '确认', }
cancelButtonText: '取消', ElMessageBox.confirm(msg, '提示', {
type: 'warning', confirmButtonText: '确认',
}) cancelButtonText: '取消',
.then(() => { type: 'warning',
deleteType(ids).then(()=>{ })
ElMessage.success('删除成功'); .then(() => {
typeList(); deleteType(ids).then(()=>{
}) ElMessage.success('删除成功');
}) getTypeData()
.catch(() => {}); checkedNodes.value = []
})
})
.catch(() => {});
}; };
// //
onMounted(() => { onMounted(() => {
initTableData(); initTableData();
}); });
/** 重置按钮操作 */ const filterNode = (value: string, data:any) => {
const resetQuery = (formEl: FormInstance | undefined) => { if (!value) return true;
if (!formEl) return return data.dictName.includes(value)
formEl.resetFields()
typeList()
}; };
// //
const handleSelectionChange = (selection:TableDataRow[])=> { const handleNodeClick = (data:TableDataRow) => {
state.ids = selection.map(item => item.dictId) selectedNode.value = data
dataViewRef.value.initTableData(data.dictId,data.dictType)
}; };
const handleCheckChange = (
data: TableDataRow,
checked: boolean,
) => {
if(checked){
checkedNodes.value.push(data)
}else{
checkedNodes.value = checkedNodes.value.filter((item:TableDataRow) => {
if(item.dictId!==data.dictId){
return true
}
return false
})
}
}
const expandedOn = ()=>{
expanded.value = !expanded.value
let treeList = dictTypeData.value as Array<TableDataRow>;
for (let i = 0; i < treeList.length; i++) {
treeRef.value.store.nodesMap[treeList[i].dictId].expanded = expanded.value;
}
}
watch(filterText, (val) => {
treeRef.value!.filter(val)
});
</script> </script>
<style scoped lang="scss">
.btm8{margin-bottom: 8px;}
.filter-tree{
height: calc(100vh - 290px);
overflow-y: auto;
}
</style>

View File

@ -2,7 +2,7 @@
<div class="personal"> <div class="personal">
<el-row> <el-row>
<!-- 个人信息 --> <!-- 个人信息 -->
<el-col :xs="24" :sm="16"> <el-col :xs="24" :sm="24">
<el-card shadow="hover" header="个人信息"> <el-card shadow="hover" header="个人信息">
<div class="personal-user"> <div class="personal-user">
<div class="personal-user-left"> <div class="personal-user-left">
@ -64,7 +64,7 @@
</el-col> </el-col>
<!-- 消息通知 --> <!-- 消息通知 -->
<el-col :xs="24" :sm="8" class="pl15 personal-info"> <!-- <el-col :xs="24" :sm="8" class="pl15 personal-info">
<el-card shadow="hover"> <el-card shadow="hover">
<template #header> <template #header>
<span>消息通知</span> <span>消息通知</span>
@ -78,10 +78,10 @@
</ul> </ul>
</div> </div>
</el-card> </el-card>
</el-col> </el-col> -->
<!-- 营销推荐 --> <!-- 营销推荐 -->
<el-col :span="24"> <!-- <el-col :span="24">
<el-card shadow="hover" class="mt15" header="营销推荐"> <el-card shadow="hover" class="mt15" header="营销推荐">
<el-row :gutter="15" class="personal-recommend-row"> <el-row :gutter="15" class="personal-recommend-row">
<el-col :sm="6" v-for="(v, k) in recommendList" :key="k" class="personal-recommend-col"> <el-col :sm="6" v-for="(v, k) in recommendList" :key="k" class="personal-recommend-col">
@ -95,7 +95,7 @@
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
</el-col> </el-col> -->
<!-- 更新信息 --> <!-- 更新信息 -->
<el-col :span="24"> <el-col :span="24">
@ -164,39 +164,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">密保手机</div>
<div class="personal-edit-safe-item-left-value">已绑定手机132****4108</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即修改</el-button>
</div>
</div>
</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">密保问题</div>
<div class="personal-edit-safe-item-left-value">已设置密保问题账号安全大幅度提升</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即设置</el-button>
</div>
</div>
</div>
<div class="personal-edit-safe-box">
<div class="personal-edit-safe-item">
<div class="personal-edit-safe-item-left">
<div class="personal-edit-safe-item-left-label">绑定QQ</div>
<div class="personal-edit-safe-item-left-value">已绑定QQ110****566</div>
</div>
<div class="personal-edit-safe-item-right">
<el-button text type="primary">立即设置</el-button>
</div>
</div>
</div>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>

View File

@ -32,10 +32,10 @@
<el-form-item label="状态">{{ proxy.getOptionValue(formData.status, statusOptions,'value','label') }}</el-form-item> <el-form-item label="状态">{{ proxy.getOptionValue(formData.status, statusOptions,'value','label') }}</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="创建者">{{ formData.createdUser.userNickname }}</el-form-item> <el-form-item label="创建者">{{ formData.createdUser?.userNickname }}</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="更新者">{{ formData.updatedUser.userNickname }}</el-form-item> <el-form-item label="更新者">{{ formData.updatedUser?.userNickname }}</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="备注信息">{{ formData.remark }}</el-form-item> <el-form-item label="备注信息">{{ formData.remark }}</el-form-item>
@ -51,9 +51,8 @@
class="btn-del" class="btn-del"
type="danger" type="danger"
size="small" size="small"
:disabled="multiple" @click="handleDelete"
@click="handleDelete(null)" ><el-icon><ele-Delete /></el-icon>清空日志</el-button>
><el-icon><ele-Delete /></el-icon>删除</el-button>
<el-divider /> <el-divider />
<el-table v-loading="logList.loading" :data="logList.data" @selection-change="handleSelectionChange"> <el-table v-loading="logList.loading" :data="logList.data" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
@ -203,31 +202,16 @@ const handleSelectionChange = (selection:Array<SysJobLogData>) => {
logList.logIds = selection.map(item => item.id) logList.logIds = selection.map(item => item.id)
multiple.value = !selection.length multiple.value = !selection.length
}; };
const handleDelete = (row: SysJobLogData) => { const handleDelete = () => {
let msg = '你确定要删除所选数据?'; ElMessageBox.confirm('你确定要清空日志?', '提示', {
let logId:number[] = [] ;
let targetName:string='';
if(row){
msg = `此操作将永久删除数据,是否继续?`
logId = [row.id]
targetName = row.targetName
}else{
logId = logList.logIds
targetName=state.formData.invokeTarget!
}
if(logId.length===0){
ElMessage.error('请选择要删除的数据。');
return
}
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
delSysJobLog(logId).then(()=>{ delSysJobLog(logList.param.targetName!).then(()=>{
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getLogList(targetName); getLogList(logList.param.targetName!);
}) })
}) })
.catch(() => {}); .catch(() => {});

View File

@ -172,12 +172,7 @@
</el-table-column> </el-table-column>
<el-table-column label="字典类型" width="160"> <el-table-column label="字典类型" width="160">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> <el-cascader v-model="scope.row.dictType" :options="dictOptions" :props="typeProps" clearable :show-all-levels="false"/>
<el-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName" :value="dict.dictType">
<span style="float: left">{{ dict.dictName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
</el-option>
</el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="关联表" width="160"> <el-table-column label="关联表" width="160">
@ -205,23 +200,31 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { inject, onBeforeMount, ref} from 'vue'; import { getCurrentInstance, inject, onBeforeMount, ref } from 'vue';
import type { FormInstance } from 'element-plus'; import type { FormInstance } from 'element-plus';
import {DictOpt, TableColumns, TableDataInfo} from '/@/views/system/tools/gen/component/model'; import {DictOpt, TableColumns} from '/@/views/system/tools/gen/component/model';
import {optionselect} from "/@/api/system/dict/type"; import {optionselect} from "/@/api/system/dict/type";
import RelationTable from "/@/views/system/tools/gen/component/relationTable.vue"; import RelationTable from "/@/views/system/tools/gen/component/relationTable.vue";
import _ from "lodash"; import _ from "lodash";
defineOptions({ name: "genTableColumns"}) defineOptions({ name: "genTableColumns"})
const {proxy} = getCurrentInstance() as any;
const relationTableRef = ref(); const relationTableRef = ref();
const tableColumnsRef = ref<FormInstance>(); const tableColumnsRef = ref<FormInstance>();
const info = inject<any>('tableData'); const info = inject<any>('tableData');
// //
const tableHeight = ref(document.documentElement.scrollHeight - 300 + 'px'); const tableHeight = ref(document.documentElement.scrollHeight - 300 + 'px');
const dictOptions = ref(<DictOpt[]>[]) const dictOptions = ref(<DictOpt[]>[])
const typeProps = ref({
value: 'dictType',
label: 'dictName',
children: 'children',
emitPath: false
})
onBeforeMount(()=>{ onBeforeMount(()=>{
// //
optionselect().then((res:any)=>{ optionselect().then((res:any)=>{
dictOptions.value = res.data.dictType??[] const data = res.data.dictType??[]
dictOptions.value = proxy.handleTree(data, 'dictId', 'pid', 'children', true)
}) })
}) })
const handleChangeConfig = (row:TableColumns)=>{ const handleChangeConfig = (row:TableColumns)=>{

View File

@ -102,7 +102,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {toRefs, reactive, onMounted, ref, defineComponent} from 'vue'; import {toRefs, reactive, onMounted, ref, defineComponent} from 'vue';
import {ElMessageBox, ElMessage, FormInstance} from 'element-plus'; import {ElMessageBox, ElMessage, FormInstance,ElLoading } from 'element-plus';
import {getTableList, deleteTables, batchGenCode, syncTable} from "/@/api/system/tools/gen"; import {getTableList, deleteTables, batchGenCode, syncTable} from "/@/api/system/tools/gen";
import {TableData,TableDataState} from "/@/views/system/tools/gen/component/model" import {TableData,TableDataState} from "/@/views/system/tools/gen/component/model"
import importTable from "/@/views/system/tools/gen/component/importTable.vue"; import importTable from "/@/views/system/tools/gen/component/importTable.vue";
@ -220,11 +220,14 @@ const handleGenTable=(row: TableData|null)=>{
type: 'warning', type: 'warning',
}) })
.then(() => { .then(() => {
const loading = ElLoading.service({text:'生成中...'})
batchGenCode(ids).then(()=>{ batchGenCode(ids).then(()=>{
ElMessage.success('生成成功'); ElMessage.success('生成成功');
resetMenuSession() resetMenuSession()
tableList(); tableList();
}) }).finally(() => {
loading.close()
})
}) })
.catch(() => {}); .catch(() => {});
} }

View File

@ -102,7 +102,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {toRefs, reactive, onMounted, ref, watch, getCurrentInstance} from 'vue'; import {toRefs, reactive, onMounted, ref, watch, getCurrentInstance} from 'vue';
import {ElTree,FormInstance} from 'element-plus'; import {ElTree,FormInstance} from 'element-plus';
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import UserList from '/@/views/system/user/component/userList.vue'; import UserList from '/@/views/system/user/component/userList.vue';
import {getDeptTree} from '/@/api/system/user'; import {getDeptTree} from '/@/api/system/user';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 KiB

View File

@ -1,51 +0,0 @@
// 地图模拟数据
export const echartsMapList: Array<object> = [
{ name: '深圳市人民政府', value: '100' },
{ name: '莲花山公园', value: '100' },
{ name: '世界之窗', value: '100' },
{ name: '华侨城欢乐谷', value: '100' },
{ name: '宝安区西乡', value: '100' },
];
// 地图经纬度数据
export const echartsMapData: object = {
: [114.064524, 22.549225],
: [114.0658, 22.560072],
: [113.979419, 22.540579],
: [113.986066, 22.548056],
西: [113.869053, 22.581714],
};
// 地图图片显示
export const echartsMapImgs: Array<object> = [
{
url: 'https://img1.baidu.com/it/u=4244861097,3561366422&fm=11&fmt=auto&gp=0.jpg',
name: '深圳市人民政府',
add: '深圳市福田区福中三路市民中心C区',
dec: '深圳市人民政府是根据《中华人民共和国地方各级人民代表大会和地方各级人民政府组织法》设立的,是深圳市人民代表大会的执行机关,是深圳市的国家行政机关。',
},
{
url: 'https://img1.baidu.com/it/u=3793608028,4006842751&fm=26&fmt=auto&gp=0.jpg',
name: '莲花山公园',
add: '广东省深圳市福田区莲花街道莲花北社区红荔路6030号',
dec: '莲花山公园筹建于1992年10月10日 1997年6月23日正式对外局部开放。',
},
{
url: 'https://img0.baidu.com/it/u=1406340112,1927292660&fm=26&fmt=auto&gp=0.jpg',
name: '世界之窗',
add: '深圳市南山区深南大道9037号',
dec: '这里,世界首座实景拍摄悬空式球幕影院“飞跃美利坚””,为游客提供集休闲放松于一体的都市时尚生活空间。',
},
{
url: 'https://img0.baidu.com/it/u=3042342330,902556630&fm=26&fmt=auto&gp=0.jpg',
name: '华侨城欢乐谷',
add: '广东省深圳市南山区沙河街道星河街社区侨城西街1号',
dec: '深圳欢乐谷注重满足人们参与、体验的新型诱游需求,营造出自然、清新、活泼、惊奇、热烈、刺激的休闲旅游氛围。',
},
{
url: 'https://img2.baidu.com/it/u=1075072079,1229283519&fm=11&fmt=auto&gp=0.jpg',
name: '宝安区西乡',
add: '西乡街道下辖25个社区',
dec: '西乡街道,隶属于广东省深圳市宝安区,位于宝安区西南部,东接石岩街道,南接新安街道,西至珠江口岸边,北接航城街道。',
},
];

View File

@ -1,131 +0,0 @@
// 顶部下来菜单
export const dropdownList: Array<object> = [
{
label: '广东省农业农村厅',
},
{
label: '广西省农业农村厅',
},
{
label: '四川省农业农村厅',
},
{
label: '湖北省农业农村厅',
},
{
label: '福建省农业农村厅',
},
{
label: '山东省农业农村厅',
},
{
label: '江西省农业农村厅',
},
];
// sky 天气
export const skyList: Array<object> = [
{
v1: '时间',
v2: '天气',
v3: '温度',
v4: '湿度',
v5: '降水概率',
v6: '风向',
v7: '风力',
type: 'title',
},
{
v1: '今天',
v2: 'ele-Sunny',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
{
v1: '明天',
v2: 'ele-Lightning',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
{
v1: '后天',
v2: 'ele-Sunny',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
];
// 当前设置状态
export const dBtnList: Array<object> = [
{
v1: '地块A-灌溉',
v2: '阳光玫瑰种植',
v3: '126天',
v4: '设备在线',
},
{
v1: '地块B-收割',
v2: '阳光玫瑰种植',
v3: '360天',
v4: '设备预警',
},
];
// 当前设备监测
export const chartData4List: Array<object> = [
{
label: '温度',
},
{
label: '光照',
},
{
label: '湿度',
},
{
label: '风力',
},
{
label: '张力',
},
{
label: '气压',
},
];
// 3DEarth 地图周围按钮组
export const earth3DBtnList: Array<object> = [
{
topLevelClass: 'fixed-top',
icon: 'ele-MagicStick',
label: '环境监测',
type: 0,
},
{
topLevelClass: 'fixed-right',
icon: 'ele-MoonNight',
label: '精准管理',
type: 1,
},
{
topLevelClass: 'fixed-bottom',
icon: 'ele-TrendCharts',
label: '数据报表',
type: 2,
},
{
topLevelClass: 'fixed-left',
icon: 'ele-Van',
label: '产品追溯',
type: 3,
},
];

View File

@ -69,6 +69,6 @@
"skipLibCheck": true /* Skip type checking of declaration files. */, "skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ "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 "include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "shim.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 "exclude": ["node_modules", "dist"] // Indicates the file directory that does not need to be compiled
} }