99 Commits
v3.8.7 ... main

Author SHA1 Message Date
RuoYi
168ad6a3b6 行内表单默认设置固定宽度 2025-12-10 09:37:56 +08:00
RuoYi
d2b4332589 优化表单构建关闭页签销毁复制插件 2025-12-04 13:03:38 +08:00
RuoYi
b086e172ae 优化生成代码下载的zip文件名 2025-12-03 10:26:48 +08:00
RuoYi
e7d8010390 登录/注册页面底部版权信息修改为读取配置 2025-12-02 15:29:17 +08:00
RuoYi
b51b7cfa04 修正命名Default 2025-12-02 13:11:06 +08:00
RuoYi
199c9951f5 修复表单构建移除所有控件后切换路由回来空白问题 2025-12-02 13:07:15 +08:00
RuoYi
b8019c9f8e 添加新群号:174569686 2025-10-05 20:10:57 +08:00
RuoYi
ccc452eb6b 修复固定头部时出现的导航栏偏移问题(ICV9OH) 2025-09-04 19:58:43 +08:00
RuoYi
abe8095320 用户导入添加验证提示 2025-08-23 11:45:28 +08:00
RuoYi
215fab2ea6 优化布局设置显示 2025-08-23 11:45:03 +08:00
RuoYi
0547a82b71 升级element-plus到最新版本2.10.7 2025-08-21 14:43:36 +08:00
RuoYi
91527eb654 用户归属部门新增清除 2025-08-21 14:40:34 +08:00
RuoYi
5b2b495da5 优化checkbox废弃API 2025-08-14 10:43:32 +08:00
RuoYi
f1be984efb columns default value 2025-08-09 16:13:36 +08:00
RuoYi
03dd370d1b 显示列信息支持对象格式 2025-08-09 13:23:04 +08:00
RuoYi
d231e28847 update transition enter 2025-07-14 12:52:18 +08:00
RuoYi
4ac33ed630 添加新群号:191164766 2025-06-20 11:39:37 +08:00
RuoYi
65b97a9018 消除控制台警告信息 2025-06-06 10:39:42 +08:00
RuoYi
4da85f6491 update element-plus version 2025-06-06 10:17:36 +08:00
RuoYi
f237f27861 升级组件依赖到最新版本 2025-06-04 15:31:48 +08:00
RuoYi
5723e17eef 若依 3.9.0 2025-05-28 08:50:55 +08:00
RuoYi
b75fd760e8 添加底部版权信息及开关 2025-05-24 14:25:13 +08:00
RuoYi
40037b8c3b 添加页签图标显示开关功能 2025-05-23 14:57:04 +08:00
RuoYi
f5b8eba57a 账号密码支持自定义更新周期 2025-05-23 09:05:10 +08:00
RuoYi
37c94666fb 初始密码支持自定义修改策略 2025-05-22 23:09:24 +08:00
RuoYi
dbe3df5edd 新增用户默认初始化密码 2025-05-22 22:41:17 +08:00
RuoYi
1428299f55 优化代码 2025-05-15 10:01:04 +08:00
RuoYi
ebe0df0d37 优化导航栏显示昵称&设置 2025-05-09 13:54:07 +08:00
RuoYi
19348408d3 菜单搜索支持键盘选择&悬浮主题背景 2025-05-07 13:23:15 +08:00
RuoYi
9ee89f7085 图片上传组件新增disabled属性 2025-05-06 19:14:23 +08:00
RuoYi
060adcbdc1 代码生成列支持拖动排序 2025-05-06 14:41:20 +08:00
RuoYi
8eee596292 修复上传组件被多次引用拖动仅对第一个有效的问题 2025-05-06 13:43:05 +08:00
RuoYi
dae8c5016c 上传组件新增拖动排序属性 2025-04-30 10:29:32 +08:00
RuoYi
c88c3d6c69 外链加载时遮罩信息提示 2025-04-30 08:25:06 +08:00
RuoYi
2d0fc59fc7 添加页签openPage支持传递参数 2025-04-27 13:43:28 +08:00
RuoYi
2f8a257efd remove all semicolons 2025-04-27 09:58:29 +08:00
RuoYi
7de94e2ea3 富文本复制粘贴图片上传至url 2025-04-24 14:21:14 +08:00
RuoYi
2aa0b4e521 update vite.config.js 2025-04-24 11:22:10 +08:00
RuoYi
a4c4653802 优化富文本控制台警告异常 2025-04-22 11:49:44 +08:00
RuoYi
06d52deb60 优化代码 2025-04-22 11:48:56 +08:00
RuoYi
f2a0f69465 显隐列组件支持全选/全不选 2025-04-21 15:29:14 +08:00
RuoYi
589151d1ed 优化菜单搜索查询页 2025-04-21 13:23:19 +08:00
RuoYi
2dbc91653a 新增默认打包配置项 2025-04-18 14:43:50 +08:00
RuoYi
ff14fa5e4e 支持文件&图片组件自定义地址&参数 2025-04-18 12:55:34 +08:00
RuoYi
c2005614bc 优化角色禁用不允许分配 2025-04-17 15:08:32 +08:00
RuoYi
42c1ea3c89 添加新群号:287842588 2025-04-01 19:14:59 +08:00
RuoYi
4b038efabf 登录页和注册页表头使用VUE_APP_TITLE配置值 2025-03-18 15:56:20 +08:00
RuoYi
ea552797b5 优化代码 2025-03-14 16:10:00 +08:00
RuoYi
d9ecdc9a53 优化代码 2025-03-04 20:31:30 +08:00
RuoYi
11889edf70 优化顶部菜单搜索栏为多层级显示 2025-03-03 12:06:57 +08:00
RuoYi
93fe80dc68 优化前端处理路由函数代码 2025-03-01 15:04:28 +08:00
RuoYi
537fc97695 优化前端树结构性能问题 2025-03-01 14:54:05 +08:00
RuoYi
ca5ae82f9e pagination更换成flex布局 2025-03-01 14:38:00 +08:00
RuoYi
094e300939 代码生成列表支持按时间排序 2025-02-28 19:45:29 +08:00
RuoYi
3393757b8c 文件上传组件新增类型 2025-02-28 19:45:03 +08:00
RuoYi
33e2eb7642 文件上传组件新增disabled属性 2025-02-28 13:09:13 +08:00
RuoYi
ec7183d094 update index.vue 2025-02-28 13:08:59 +08:00
RuoYi
865100e487 添加新群号 2025-01-15 15:07:18 +08:00
RuoYi
ef417f3e90 copyright 2025 2025-01-07 10:41:45 +08:00
RuoYi
31cffcb9d4 若依 3.8.9 2024-12-30 08:35:42 +08:00
RuoYi
7701ec465c 优化特殊字符密码修改失败问题 2024-12-17 14:26:57 +08:00
RuoYi
8b97a0c09b 优化TopNav内链菜单点击没有高亮(IB8WHJ) 2024-12-17 14:26:23 +08:00
RuoYi
5382a52d22 update README 2024-12-13 20:07:07 +08:00
RuoYi
c89ba258bc 用户管理过滤掉已禁用部门(IB5H7F) 2024-12-11 11:20:59 +08:00
RuoYi
7644a87718 优化代码 2024-12-05 16:45:54 +08:00
RuoYi
4db1eedc6a 新增表单构建功能 2024-12-05 10:52:33 +08:00
RuoYi
e212a0ab75 支持开启暗黑模式 2024-12-04 20:32:06 +08:00
RuoYi
535c5bd814 白名单支持对通配符路径匹配 2024-12-04 08:49:18 +08:00
RuoYi
497f98a2a8 修复默认关闭Tags-Views时,内链页面打不开 2024-11-27 19:50:47 +08:00
RuoYi
6061c3548f 优化代码 2024-11-25 22:20:30 +08:00
RuoYi
f8c4968b33 面板兼容移动端显示 2024-11-25 15:43:05 +08:00
RuoYi
e986a4c537 参数键值更换为多行文本 2024-11-25 12:16:10 +08:00
RuoYi
3e029957af 菜单面包屑导航支持多层级显示 2024-11-22 20:59:14 +08:00
RuoYi
d981cac3f3 分栏参数微调 2024-11-22 14:49:14 +08:00
RuoYi
341728774c 用户管理支持分栏拖动 2024-11-22 12:58:50 +08:00
RuoYi
e82ee73704 用户头像http(s)链接支持 2024-11-20 10:47:31 +08:00
RuoYi
2a59dbfbb3 校检文件名是否包含特殊字符 2024-11-05 12:50:44 +08:00
RuoYi
341e3be3a9 优化字典数组条件判断 2024-10-29 16:34:56 +08:00
RuoYi
7507a187aa 优化上传图片带域名不增加前缀 2024-10-21 16:56:24 +08:00
RuoYi
d02ba57ffb 修复代码生成上级菜单显示问题 2024-09-27 16:02:40 +08:00
RuoYi
f476e35522 删除多余的判断 2024-09-27 12:43:54 +08:00
RuoYi
1c2da6dfb1 修改导出文件名称 2024-09-11 14:19:21 +08:00
RuoYi
bbb45eb72e avatar add headers 2024-07-02 16:08:11 +08:00
RuoYi
5d8172a117 cron生成的表达式hour优化 2024-07-01 16:16:35 +08:00
RuoYi
c01ac5ccac 若依 3.8.8 2024-06-30 08:01:12 +08:00
RuoYi
0780d9659b 修复移动端左侧菜单无法显示问题 2024-06-29 21:44:21 +08:00
RuoYi
c227751acb 菜单管理新增路由名称 2024-06-29 18:53:38 +08:00
RuoYi
30f52f9946 fix repeat getList 2024-06-29 16:21:36 +08:00
RuoYi
4805cc9ea5 remove merge dictTag type 2024-06-29 15:59:59 +08:00
若依
1abf0641b0 Merge pull request #287 from 593496637/main
修复升级element-plus出现的警告⚠️提示
2024-06-29 15:39:18 +08:00
likaikai
e3d4018c14 chore:1.修复升级element-plus后el-radio使用label过期的问题。2.修复DictTag组件缺少type的问题 2024-06-29 14:11:19 +08:00
RuoYi
cae65bb74b 升级组件依赖到最新版本 2024-06-28 17:02:24 +08:00
RuoYi
6666ce5526 优化代码 2024-06-28 17:01:39 +08:00
RuoYi
49d0fc0bdc 添加新群号:151450850 2024-05-29 14:49:15 +08:00
RuoYi
84b3565732 update copyright 2024 2024-03-11 10:47:10 +08:00
RuoYi
ea4a870302 添加新群号:138988063 2024-03-11 10:46:45 +08:00
RuoYi
6a10846b6d 用户密码新增非法字符验证 2024-03-01 21:54:12 +08:00
RuoYi
69c189a0c9 代码生成新增创建表结构功能 2024-03-01 20:08:52 +08:00
RuoYi
1270e6e3c9 添加新群号:161281055 2023-12-13 11:18:51 +08:00
129 changed files with 7558 additions and 3352 deletions

View File

@@ -1,21 +1,20 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.7</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.0</h1>
<h4 align="center">基于SpringBoot+Vue3前后端分离的Java快速开发框架</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.7-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.0-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast) 版本。
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://gitcode.com/yangzongzhuan/RuoYi-Vue-fast) 版本。
* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
## 前端运行
@@ -106,4 +105,4 @@ yarn dev
## 若依前后端分离交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) 点击按钮入群。
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/已满-167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [![加入QQ群](https://img.shields.io/badge/已满-104748341-blue.svg)](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [![加入QQ群](https://img.shields.io/badge/已满-160110482-blue.svg)](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [![加入QQ群](https://img.shields.io/badge/已满-170801498-blue.svg)](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [![加入QQ群](https://img.shields.io/badge/已满-108482800-blue.svg)](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [![加入QQ群](https://img.shields.io/badge/已满-101046199-blue.svg)](https://jq.qq.com/?_wv=1027&k=SpyH2875) [![加入QQ群](https://img.shields.io/badge/已满-136919097-blue.svg)](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [![加入QQ群](https://img.shields.io/badge/已满-143961921-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [![加入QQ群](https://img.shields.io/badge/已满-174951577-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [![加入QQ群](https://img.shields.io/badge/已满-161281055-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [![加入QQ群](https://img.shields.io/badge/已满-138988063-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [![加入QQ群](https://img.shields.io/badge/已满-151450850-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [![加入QQ群](https://img.shields.io/badge/已满-224622315-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [![加入QQ群](https://img.shields.io/badge/已满-287842588-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [![加入QQ群](https://img.shields.io/badge/已满-187944233-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [![加入QQ群](https://img.shields.io/badge/已满-228578329-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [![加入QQ群](https://img.shields.io/badge/已满-191164766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) [![加入QQ群](https://img.shields.io/badge/174569686-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=PmYavuzsOthVqfdAPbo4uAeIbu7Ttjgc&authKey=p52l8%2FXa4PS1JcEmS3VccKSwOPJUZ1ZfQ69MEKzbrooNUljRtlKjvsXf04bxNp3G&noverify=0&group_code=174569686) 点击按钮入群。

View File

@@ -1,6 +1,6 @@
{
"name": "ruoyi",
"version": "3.8.7",
"version": "3.9.0",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
@@ -18,28 +18,34 @@
"dependencies": {
"@element-plus/icons-vue": "2.3.1",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "10.6.1",
"axios": "0.27.2",
"echarts": "5.4.3",
"element-plus": "2.4.3",
"@vueuse/core": "13.3.0",
"axios": "1.9.0",
"clipboard": "2.0.11",
"echarts": "5.6.0",
"element-plus": "2.10.7",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"vue": "3.3.9",
"pinia": "3.0.2",
"splitpanes": "4.0.4",
"vue": "3.5.16",
"vue-cropper": "1.1.1",
"vue-router": "4.2.5"
"vue-router": "4.5.1",
"vuedraggable": "4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.5.0",
"@vue/compiler-sfc": "3.3.9",
"sass": "1.69.5",
"unplugin-auto-import": "0.17.1",
"vite": "5.0.4",
"@vitejs/plugin-vue": "5.2.4",
"sass-embedded": "1.89.1",
"unplugin-auto-import": "0.18.6",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "6.3.5",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1",
"unplugin-vue-setup-extend-plus": "1.0.0"
"vite-plugin-svg-icons": "2.0.1"
},
"overrides": {
"quill": "2.0.2"
}
}

View File

@@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) {
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
params: data
data: data
})
}
@@ -105,6 +105,7 @@ export function uploadAvatar(data) {
return request({
url: '/system/user/profile/avatar',
method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: data
})
}

View File

@@ -43,6 +43,15 @@ export function importTable(data) {
})
}
// 创建表
export function createTable(data) {
return request({
url: '/tool/gen/createTable',
method: 'post',
params: data
})
}
// 预览生成代码
export function previewTable(tableId) {
return request({

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746590936918" class="icon" viewBox="0 0 1194 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" xmlns:xlink="http://www.w3.org/1999/xlink" width="233.203125" height="200"><path d="M1151.9144 325.11999969V89.12a57.04000031 57.04000031 0 0 0-28.8-49.44 58.15999969 58.15999969 0 0 0-57.76000031 0 57.04000031 57.04000031 0 0 0-28.8 49.44v235.99999969c0.24 84.31999969-33.6 152.56000031-94.08 212.00000062-60.07999969 59.83999969-141.84 80.64-227.04 80.4H225.91440031L388.07439969 457.11999969a56.80000031 56.80000031 0 0 0 12.40000031-62.16 57.76000031 57.76000031 0 0 0-94.00000031-18.63999938L48.8744 631.20000031a56.88 56.88 0 0 0 0 80.79999938l264.96 262.56a58.08 58.08 0 0 0 96.55999969-25.59999938 56.80000031 56.80000031 0 0 0-14.95999969-55.2L232.07439969 731.67999969h483.44000062c116.56000031 0 226.15999969-32.08000031 308.64-113.76 82.15999969-80.80000031 128.23999969-178.15999969 127.83999938-292.87999969" p-id="5379"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733303018722" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1447" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M368.832 67.2c51.328-16.384 89.216 34.112 75.712 76.416a346.816 346.816 0 0 0 435.84 435.84c42.304-13.44 92.8 24.384 76.48 75.712A467.968 467.968 0 1 1 368.832 67.2z m-35.776 122.688a368.832 368.832 0 1 0 501.056 501.056 445.952 445.952 0 0 1-501.056-501.056z" p-id="1448"></path></svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746760911144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12537" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M395.21211 182.914448c0 62.669318 49.541323 113.472378 110.642936 113.472378 61.093427 0 110.652146-50.80306 110.65214601-113.472378 0-62.685691-49.559742-113.487727-110.65214601-113.487727C444.75241 69.426721 395.21211 120.22978 395.21211 182.914448zM395.21211 523.34693101c0 62.668295 49.541323 113.487727 110.642936 113.48772699 61.093427 0 110.652146-50.820456 110.652146-113.487727 0-62.669318-49.559742-113.472378-110.652146-113.472378C444.75241 409.874553 395.21211 460.67761301 395.21211 523.34693101zM395.21211 841.084529c0 62.686714 49.541323 113.488751 110.642936 113.488751 61.093427 0 110.652146-50.80203599 110.65214601-113.488751 0-62.669318-49.559742-113.471354-110.65214601-113.471354C444.75241 727.614198 395.21211 778.416234 395.21211 841.084529z" p-id="12538"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733303115132" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12397" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 890.432c18.432 0 33.408 14.976 33.408 33.408v66.752a33.408 33.408 0 0 1-66.816 0v-66.752c0-18.432 14.976-33.408 33.408-33.408z m-267.52-110.848a33.408 33.408 0 0 1 0 47.232l-47.296 47.232a33.408 33.408 0 0 1-47.232-47.232l47.232-47.232a33.408 33.408 0 0 1 47.232 0z m582.336 0l47.232 47.232a33.408 33.408 0 0 1-47.232 47.232l-47.232-47.232a33.408 33.408 0 1 1 47.232-47.232zM512 200.32a311.68 311.68 0 1 1 0 623.296 311.68 311.68 0 0 1 0-623.36z m0 66.752a244.864 244.864 0 1 0 0 489.728 244.864 244.864 0 0 0 0-489.728zM100.16 478.592a33.408 33.408 0 1 1 0 66.816H33.408a33.408 33.408 0 0 1 0-66.816h66.752z m890.432 0a33.408 33.408 0 0 1 0 66.816h-66.752a33.408 33.408 0 1 1 0-66.816h66.752zM197.184 149.952l47.232 47.232a33.408 33.408 0 1 1-47.232 47.232l-47.232-47.232a33.408 33.408 0 0 1 47.232-47.232z m676.864 0a33.408 33.408 0 0 1 0 47.232l-47.232 47.232a33.408 33.408 0 1 1-47.232-47.232l47.232-47.232a33.408 33.408 0 0 1 47.232 0zM512 0c18.432 0 33.408 14.976 33.408 33.408v66.752a33.408 33.408 0 1 1-66.816 0V33.408C478.592 14.976 493.568 0 512 0z" p-id="12398"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,4 +1,4 @@
@import './variables.module.scss';
@use './variables.module.scss' as *;
@mixin colorBtn($color) {
background: $color;

View File

@@ -1,10 +1,9 @@
@import './variables.module.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './ruoyi.scss';
@use './mixin.scss';
@use './transition.scss';
@use './element-ui.scss';
@use './sidebar.scss';
@use './btn.scss';
@use './ruoyi.scss';
body {
height: 100%;
@@ -131,10 +130,6 @@ aside {
position: relative;
}
.pagination-container {
margin-top: 30px;
}
.text-center {
text-align: center
}

View File

@@ -1,200 +1,212 @@
/**
/**
* 通用css样式布局处理
* Copyright (c) 2019 ruoyi
*/
/** 基础通用 **/
/** 基础通用 **/
.pt5 {
padding-top: 5px;
padding-top: 5px;
}
.pr5 {
padding-right: 5px;
padding-right: 5px;
}
.pb5 {
padding-bottom: 5px;
padding-bottom: 5px;
}
.mt5 {
margin-top: 5px;
margin-top: 5px;
}
.mr5 {
margin-right: 5px;
margin-right: 5px;
}
.mb5 {
margin-bottom: 5px;
margin-bottom: 5px;
}
.mb8 {
margin-bottom: 8px;
margin-bottom: 8px;
}
.ml5 {
margin-left: 5px;
margin-left: 5px;
}
.mt10 {
margin-top: 10px;
margin-top: 10px;
}
.mr10 {
margin-right: 10px;
margin-right: 10px;
}
.mb10 {
margin-bottom: 10px;
margin-bottom: 10px;
}
.ml10 {
margin-left: 10px;
margin-left: 10px;
}
.mt20 {
margin-top: 20px;
margin-top: 20px;
}
.mr20 {
margin-right: 20px;
margin-right: 20px;
}
.mb20 {
margin-bottom: 20px;
margin-bottom: 20px;
}
.ml20 {
margin-left: 20px;
margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.el-form--inline {
.el-form-item {
.el-input, .el-cascader, .el-select, .el-autocomplete {
width: 200px;
}
}
}
.el-form .el-form-item__label {
font-weight: 700;
font-weight: 700;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
margin-top: 6vh !important;
}
.el-dialog.scrollbar .el-dialog__body {
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 10px 20px 0;
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 10px 20px 0;
}
.el-table {
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9 !important;
color: #515a6e;
height: 40px !important;
font-size: 13px;
}
}
.el-table__body-wrapper {
.el-button [class*="el-icon-"] + span {
margin-left: 1px;
}
}
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9 !important;
color: #515a6e;
height: 40px !important;
font-size: 13px;
}
}
.el-table__body-wrapper {
.el-button [class*="el-icon-"] + span {
margin-left: 1px;
}
}
}
/** 表单布局 **/
.form-header {
font-size:15px;
color:#6379bb;
border-bottom:1px solid #ddd;
margin:8px 10px 25px 10px;
padding-bottom:5px
font-size:15px;
color:#6379bb;
border-bottom:1px solid #ddd;
margin:8px 10px 25px 10px;
padding-bottom:5px
}
/** 表格布局 **/
.pagination-container {
position: relative;
height: 25px;
margin-bottom: 10px;
margin-top: 15px;
padding: 10px 20px !important;
display: flex;
justify-content: flex-end;
margin-top: 20px;
background-color: transparent !important;
}
/* 弹窗中的分页器 */
.el-dialog .pagination-container {
position: static !important;
position: static !important;
margin: 10px 0 0 0;
padding: 0 !important;
.el-pagination {
position: static;
}
}
/* 移动端适配 */
@media (max-width: 768px) {
.pagination-container {
.el-pagination {
> .el-pagination__jump {
display: none !important;
}
> .el-pagination__sizes {
display: none !important;
}
}
}
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #FFFFFF none;
border-radius:4px;
width: 100%;
}
.pagination-container .el-pagination {
right: 0;
position: absolute;
}
@media ( max-width : 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
margin-top: 5px;
border: 1px solid var(--el-border-color-light, #e5e6e7);
background: var(--el-bg-color, #FFFFFF) none;
border-radius:4px;
width: 100%;
}
.el-table .fixed-width .el-button--small {
padding-left: 0;
padding-right: 0;
width: inherit;
padding-left: 0;
padding-right: 0;
width: inherit;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link {
cursor: pointer;
color: #409EFF;
margin-left: 10px;
cursor: pointer;
color: #409EFF;
margin-left: 10px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
font-size: 12px;
font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
margin-right: 8px;
margin-right: 8px;
}
.list-group-striped > .list-group-item {
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.list-group {
padding-left: 0px;
list-style: none;
padding-left: 0px;
list-style: none;
}
.list-group-item {
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0px;
font-size: 13px;
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0px;
font-size: 13px;
}
.pull-right {
float: right !important;
float: right !important;
}
.el-card__header {
padding: 14px 15px 7px !important;
min-height: 40px;
padding: 14px 15px 7px !important;
min-height: 40px;
}
.el-card__body {
padding: 15px 20px 20px 20px !important;
padding: 15px 20px 20px 20px !important;
}
.card-box {
padding-right: 15px;
padding-left: 15px;
margin-bottom: 10px;
margin-bottom: 10px;
}
/* button color */
@@ -220,62 +232,67 @@
/* text color */
.text-navy {
color: #1ab394;
color: #1ab394;
}
.text-primary {
color: inherit;
color: inherit;
}
.text-success {
color: #1c84c6;
color: #1c84c6;
}
.text-info {
color: #23c6c8;
color: #23c6c8;
}
.text-warning {
color: #f8ac59;
color: #f8ac59;
}
.text-danger {
color: #ed5565;
color: #ed5565;
}
.text-muted {
color: #888888;
color: #888888;
}
/* image */
.img-circle {
border-radius: 50%;
border-radius: 50%;
}
.img-lg {
width: 120px;
height: 120px;
width: 120px;
height: 120px;
}
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
}
/* 拖拽列样式 */
.sortable-ghost{
opacity: .8;
color: #fff!important;
background: #42b983!important;
opacity: .8;
color: #fff!important;
background: #42b983!important;
}
/* 表格右侧工具栏样式 */
.top-right-btn {
margin-left: auto;
margin-left: auto;
}
/* 分割面板样式 */
.splitpanes.default-theme .splitpanes__pane {
background-color: var(--splitpanes-default-bg) !important;
}

View File

@@ -1,9 +1,11 @@
@use './variables.module.scss' as vars;
#app {
.main-container {
height: 100%;
min-height: 100%;
transition: margin-left .28s;
margin-left: $base-sidebar-width;
margin-left: vars.$base-sidebar-width;
position: relative;
}
@@ -12,10 +14,8 @@
}
.sidebar-container {
-webkit-transition: width .28s;
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
width: vars.$base-sidebar-width !important;
height: 100%;
position: fixed;
font-size: 0px;
@@ -25,7 +25,7 @@
z-index: 1001;
overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
// reset element-ui css
.horizontal-collapse-transition {
@@ -89,12 +89,12 @@
}
& .theme-dark .is-active > .el-sub-menu__title {
color: $base-menu-color-active !important;
color: vars.$base-menu-color-active !important;
}
& .nest-menu .el-sub-menu>.el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: $base-sidebar-width !important;
min-width: vars.$base-sidebar-width !important;
&:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
@@ -103,10 +103,10 @@
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item {
background-color: $base-sub-menu-background !important;
background-color: vars.$base-sub-menu-background;
&:hover {
background-color: $base-sub-menu-hover !important;
background-color: vars.$base-sub-menu-hover !important;
}
}
}
@@ -169,7 +169,7 @@
}
.el-menu--collapse .el-menu .el-sub-menu {
min-width: $base-sidebar-width !important;
min-width: vars.$base-sidebar-width !important;
}
// mobile responsive
@@ -180,14 +180,14 @@
.sidebar-container {
transition: transform .28s;
width: $base-sidebar-width !important;
width: vars.$base-sidebar-width !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$base-sidebar-width, 0, 0);
transform: translate3d(-(vars.$base-sidebar-width), 0, 0);
}
}
}

View File

@@ -6,7 +6,7 @@
transition: opacity 0.28s;
}
.fade-enter,
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
@@ -18,7 +18,7 @@
transition: all .5s;
}
.fade-transform-enter {
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
}
@@ -34,7 +34,7 @@
transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);

View File

@@ -1,6 +1,6 @@
// base color
$blue: #324157;
$light-blue: #3A71A8;
$light-blue: #333c46;
$red: #C03639;
$pink: #E65D6E;
$green: #30B08F;
@@ -8,58 +8,214 @@ $tiffany: #4AB7BD;
$yellow: #FEC171;
$panGreen: #30B08F;
// 默认菜单主题风格
// 默认主题变量
$menuText: #bfcbd9;
$menuActiveText: #409eff;
$menuBg: #304156;
$menuHover: #263445;
// 浅色主题theme-light
$menuLightBg: #ffffff;
$menuLightHover: #f0f1f5;
$menuLightText: #303133;
$menuLightActiveText: #409EFF;
// 基础变量
$base-sidebar-width: 200px;
$sideBarWidth: 200px;
// 菜单暗色变量
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;
// 自定义暗色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/
// 组件变量
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
$base-sidebar-width: 200px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background;
menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color;
primaryColor: $--color-primary;
successColor: $--color-success;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
menuText: $menuText;
menuActiveText: $menuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
menuLightBg: $menuLightBg;
menuLightHover: $menuLightHover;
menuLightText: $menuLightText;
menuLightActiveText: $menuLightActiveText;
sideBarWidth: $sideBarWidth;
// 导出基础颜色
blue: $blue;
lightBlue: $light-blue;
red: $red;
pink: $pink;
green: $green;
tiffany: $tiffany;
yellow: $yellow;
panGreen: $panGreen;
// 导出组件颜色
colorPrimary: $--color-primary;
colorSuccess: $--color-success;
colorWarning: $--color-warning;
colorDanger: $--color-danger;
colorInfo: $--color-info;
}
// CSS变量定义
:root {
/* 亮色模式变量 */
--sidebar-bg: #{$menuBg};
--sidebar-text: #{$menuText};
--menu-hover: #{$menuHover};
--navbar-bg: #ffffff;
--navbar-text: #303133;
/* splitpanes default-theme 变量 */
--splitpanes-default-bg: #ffffff;
}
// 暗黑模式变量
html.dark {
/* 默认通用 */
--el-bg-color: #141414;
--el-bg-color-overlay: #1d1e1f;
--el-text-color-primary: #ffffff;
--el-text-color-regular: #d0d0d0;
--el-border-color: #434343;
--el-border-color-light: #434343;
/* 侧边栏 */
--sidebar-bg: #141414;
--sidebar-text: #ffffff;
--menu-hover: #2d2d2d;
--menu-active-text: #{$menuActiveText};
/* 顶部导航栏 */
--navbar-bg: #141414;
--navbar-text: #ffffff;
--navbar-hover: #141414;
/* 标签栏 */
--tags-bg: #141414;
--tags-item-bg: #1d1e1f;
--tags-item-border: #303030;
--tags-item-text: #d0d0d0;
--tags-item-hover: #2d2d2d;
--tags-close-hover: #64666a;
/* splitpanes 组件暗黑模式变量 */
--splitpanes-bg: #141414;
--splitpanes-border: #303030;
--splitpanes-splitter-bg: #1d1e1f;
--splitpanes-splitter-hover-bg: #2d2d2d;
/* blockquote 暗黑模式变量 */
--blockquote-bg: #1d1e1f;
--blockquote-border: #303030;
--blockquote-text: #d0d0d0;
/* Cron 时间表达式 模式变量 */
--cron-border: #303030;
/* splitpanes default-theme 暗黑模式变量 */
--splitpanes-default-bg: #141414;
/* 侧边栏菜单覆盖 */
.sidebar-container {
.el-menu-item, .menu-title {
color: var(--el-text-color-regular);
}
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item {
background-color: var(--el-bg-color) !important;
}
}
/* 顶部栏栏菜单覆盖 */
.el-menu--horizontal {
.el-menu-item {
&:not(.is-disabled) {
&:hover,
&:focus {
background-color: var(--navbar-hover) !important;
}
}
}
}
/* 分割窗格覆盖 */
.splitpanes {
background-color: var(--splitpanes-bg);
.splitpanes__pane {
background-color: var(--splitpanes-bg);
border-color: var(--splitpanes-border);
}
.splitpanes__splitter {
background-color: var(--splitpanes-splitter-bg);
border-color: var(--splitpanes-border);
&:hover {
background-color: var(--splitpanes-splitter-hover-bg);
}
&:before,
&:after {
background-color: var(--splitpanes-border);
}
}
}
/* 表格样式覆盖 */
.el-table {
--el-table-header-bg-color: var(--el-bg-color-overlay) !important;
--el-table-header-text-color: var(--el-text-color-regular) !important;
--el-table-border-color: var(--el-border-color-light) !important;
--el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
background-color: var(--el-bg-color-overlay, #f8f8f9) !important;
color: var(--el-text-color-regular, #515a6e);
}
}
}
/* 树组件高亮样式覆盖 */
.el-tree {
.el-tree-node.is-current > .el-tree-node__content {
background-color: var(--el-bg-color-overlay) !important;
color: var(--el-color-primary);
}
.el-tree-node__content:hover {
background-color: var(--el-bg-color-overlay);
}
}
/* 下拉菜单样式覆盖 */
.el-dropdown-menu__item:not(.is-disabled):focus, .el-dropdown-menu__item:not(.is-disabled):hover{
background-color: var(--navbar-hover) !important;
}
/* blockquote样式覆盖 */
blockquote {
background-color: var(--blockquote-bg) !important;
border-left-color: var(--blockquote-border) !important;
color: var(--blockquote-text) !important;
}
/* 时间表达式标题样式覆盖 */
.popup-result .title {
background: var(--cron-border);
}
}

View File

@@ -1,7 +1,7 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
@@ -10,21 +10,53 @@
</template>
<script setup>
const route = useRoute();
const router = useRouter();
import usePermissionStore from '@/store/modules/permission'
const route = useRoute()
const router = useRouter()
const permissionStore = usePermissionStore()
const levelList = ref([])
function getBreadcrumb() {
// only show routes with meta.title
let matched = route.matched.filter(item => item.meta && item.meta.title);
const first = matched[0]
// 判断是否为首页
if (!isDashboard(first)) {
matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)
let matched = []
const pathNum = findPathNum(route.path)
// multi-level menu
if (pathNum > 2) {
const reg = /\/\w+/gi
const pathList = route.path.match(reg).map((item, index) => {
if (index !== 0) item = item.slice(1)
return item
})
getMatched(pathList, permissionStore.defaultRoutes, matched)
} else {
matched = route.matched.filter((item) => item.meta && item.meta.title)
}
// 判断是否为首页
if (!isDashboard(matched[0])) {
matched = [{ path: "/index", meta: { title: "首页" } }].concat(matched)
}
levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
function findPathNum(str, char = "/") {
let index = str.indexOf(char)
let num = 0
while (index !== -1) {
num++
index = str.indexOf(char, index + 1)
}
return num
}
function getMatched(pathList, routeList, matched) {
let data = routeList.find(item => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0])
if (data) {
matched.push(data)
if (data.children && pathList.length) {
pathList.shift()
getMatched(pathList, data.children, matched)
}
}
}
function isDashboard(route) {
const name = route && route.name
if (!name) {
@@ -48,7 +80,7 @@ watchEffect(() => {
}
getBreadcrumb()
})
getBreadcrumb();
getBreadcrumb()
</script>
<style lang='scss' scoped>

View File

@@ -1,19 +1,19 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
允许的通配符[, - * ? / L W]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
周期从
<el-input-number v-model='cycle01' :min="1" :max="30" /> -
<el-input-number v-model='cycle02' :min="cycle01 + 1" :max="31" />
@@ -21,7 +21,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
<el-input-number v-model='average01' :min="1" :max="30" /> 号开始
<el-input-number v-model='average02' :min="1" :max="31 - average01" /> 日执行一次
@@ -29,20 +29,20 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
<el-radio v-model='radioValue' :value="5">
每月
<el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
<el-radio v-model='radioValue' :value="6">
本月最后一天
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="7">
<el-radio v-model='radioValue' :value="7">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 31" :key="item" :label="item" :value="item" />

View File

@@ -1,13 +1,13 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
小时允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="22" /> -
<el-input-number v-model='cycle02' :min="cycle01 + 1" :max="23" />
@@ -15,7 +15,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
<el-input-number v-model='average01' :min="0" :max="22" /> 时开始
<el-input-number v-model='average02' :min="1" :max="23 - average01" /> 小时执行一次
@@ -23,7 +23,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 24" :key="item" :label="item - 1" :value="item - 1" />
@@ -77,6 +77,12 @@ const checkboxString = computed(() => {
watch(() => props.cron.hour, value => changeRadioValue(value))
watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
function changeRadioValue(value) {
if (props.cron.min === '*') {
emit('update', 'min', '0', 'hour')
}
if (props.cron.second === '*') {
emit('update', 'second', '0', 'hour')
}
if (value === '*') {
radioValue.value = 1
} else if (value.indexOf('-') > -1) {

View File

@@ -70,42 +70,46 @@
<p class="title">时间表达式</p>
<table>
<thead>
<th v-for="item of tabTitles" :key="item">{{item}}</th>
<th>Cron 表达式</th>
<tr>
<th v-for="item of tabTitles" :key="item">{{item}}</th>
<th>Cron 表达式</th>
</tr>
</thead>
<tbody>
<td>
<span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
<el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
<el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
<el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
<el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
<el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
<el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
<el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
</td>
<td class="result">
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
</td>
<tr>
<td>
<span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
<el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
<el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
<el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
<el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
<el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
<el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
</td>
<td>
<span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
<el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
</td>
<td class="result">
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
</td>
</tr>
</tbody>
</table>
</div>
@@ -251,7 +255,6 @@ onMounted(() => {
.popup-main {
position: relative;
margin: 10px auto;
background: #fff;
border-radius: 5px;
font-size: 12px;
overflow: hidden;

View File

@@ -1,13 +1,13 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
分钟允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" /> 分钟
@@ -15,7 +15,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="1" :max="59 - average01" /> 分钟执行一次
@@ -23,7 +23,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />

View File

@@ -1,13 +1,13 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
周期从
<el-input-number v-model='cycle01' :min="1" :max="11" /> -
<el-input-number v-model='cycle02' :min="cycle01 + 1" :max="12" />
@@ -15,7 +15,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
<el-input-number v-model='average01' :min="1" :max="11" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12 - average01" /> 月月执行一次
@@ -23,7 +23,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8">
<el-option v-for="item in monthList" :key="item.key" :label="item.value" :value="item.key" />

View File

@@ -26,289 +26,289 @@ watch(() => props.ex, () => expressionChange())
// 表达式值变化时,开始去计算结果
function expressionChange() {
// 计算开始-隐藏结果
isShow.value = false;
isShow.value = false
// 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年]
let ruleArr = props.ex.split(' ');
let ruleArr = props.ex.split(' ')
// 用于记录进入循环的次数
let nums = 0;
let nums = 0
// 用于暂时存符号时间规则结果的数组
let resultArr = [];
let resultArr = []
// 获取当前时间精确至[年、月、日、时、分、秒]
let nTime = new Date();
let nYear = nTime.getFullYear();
let nMonth = nTime.getMonth() + 1;
let nDay = nTime.getDate();
let nHour = nTime.getHours();
let nMin = nTime.getMinutes();
let nSecond = nTime.getSeconds();
let nTime = new Date()
let nYear = nTime.getFullYear()
let nMonth = nTime.getMonth() + 1
let nDay = nTime.getDate()
let nHour = nTime.getHours()
let nMin = nTime.getMinutes()
let nSecond = nTime.getSeconds()
// 根据规则获取到近100年可能年数组、月数组等等
getSecondArr(ruleArr[0]);
getMinArr(ruleArr[1]);
getHourArr(ruleArr[2]);
getDayArr(ruleArr[3]);
getMonthArr(ruleArr[4]);
getWeekArr(ruleArr[5]);
getYearArr(ruleArr[6], nYear);
getSecondArr(ruleArr[0])
getMinArr(ruleArr[1])
getHourArr(ruleArr[2])
getDayArr(ruleArr[3])
getMonthArr(ruleArr[4])
getWeekArr(ruleArr[5])
getYearArr(ruleArr[6], nYear)
// 将获取到的数组赋值-方便使用
let sDate = dateArr.value[0];
let mDate = dateArr.value[1];
let hDate = dateArr.value[2];
let DDate = dateArr.value[3];
let MDate = dateArr.value[4];
let YDate = dateArr.value[5];
let sDate = dateArr.value[0]
let mDate = dateArr.value[1]
let hDate = dateArr.value[2]
let DDate = dateArr.value[3]
let MDate = dateArr.value[4]
let YDate = dateArr.value[5]
// 获取当前时间在数组中的索引
let sIdx = getIndex(sDate, nSecond);
let mIdx = getIndex(mDate, nMin);
let hIdx = getIndex(hDate, nHour);
let DIdx = getIndex(DDate, nDay);
let MIdx = getIndex(MDate, nMonth);
let YIdx = getIndex(YDate, nYear);
let sIdx = getIndex(sDate, nSecond)
let mIdx = getIndex(mDate, nMin)
let hIdx = getIndex(hDate, nHour)
let DIdx = getIndex(DDate, nDay)
let MIdx = getIndex(MDate, nMonth)
let YIdx = getIndex(YDate, nYear)
// 重置月日时分秒的函数(后面用的比较多)
const resetSecond = function () {
sIdx = 0;
sIdx = 0
nSecond = sDate[sIdx]
}
const resetMin = function () {
mIdx = 0;
mIdx = 0
nMin = mDate[mIdx]
resetSecond();
resetSecond()
}
const resetHour = function () {
hIdx = 0;
hIdx = 0
nHour = hDate[hIdx]
resetMin();
resetMin()
}
const resetDay = function () {
DIdx = 0;
DIdx = 0
nDay = DDate[DIdx]
resetHour();
resetHour()
}
const resetMonth = function () {
MIdx = 0;
MIdx = 0
nMonth = MDate[MIdx]
resetDay();
resetDay()
}
// 如果当前年份不为数组中当前值
if (nYear !== YDate[YIdx]) {
resetMonth();
resetMonth()
}
// 如果当前月份不为数组中当前值
if (nMonth !== MDate[MIdx]) {
resetDay();
resetDay()
}
// 如果当前“日”不为数组中当前值
if (nDay !== DDate[DIdx]) {
resetHour();
resetHour()
}
// 如果当前“时”不为数组中当前值
if (nHour !== hDate[hIdx]) {
resetMin();
resetMin()
}
// 如果当前“分”不为数组中当前值
if (nMin !== mDate[mIdx]) {
resetSecond();
resetSecond()
}
// 循环年份数组
goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
let YY = YDate[Yi];
let YY = YDate[Yi]
// 如果到达最大值时
if (nMonth > MDate[MDate.length - 1]) {
resetMonth();
continue;
resetMonth()
continue
}
// 循环月份数组
goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
// 赋值、方便后面运算
let MM = MDate[Mi];
MM = MM < 10 ? '0' + MM : MM;
MM = MM < 10 ? '0' + MM : MM
// 如果到达最大值时
if (nDay > DDate[DDate.length - 1]) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue;
continue
}
// 循环日期数组
goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
// 赋值、方便后面运算
let DD = DDate[Di];
let thisDD = DD < 10 ? '0' + DD : DD;
let DD = DDate[Di]
let thisDD = DD < 10 ? '0' + DD : DD
// 如果到达最大值时
if (nHour > hDate[hDate.length - 1]) {
resetHour();
resetHour()
if (Di === DDate.length - 1) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue goMonth;
continue goMonth
}
continue;
continue
}
// 判断日期的合法性,不合法的话也是跳出当前循环
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && dayRule.value !== 'workDay' && dayRule.value !== 'lastWeek' && dayRule.value !== 'lastDay') {
resetDay();
continue goMonth;
resetDay()
continue goMonth
}
// 如果日期规则中有值时
if (dayRule.value === 'lastDay') {
// 如果不是合法日期则需要将前将日期调到合法日期即月末最后一天
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
DD--
thisDD = DD < 10 ? '0' + DD : DD
}
}
} else if (dayRule.value === 'workDay') {
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
DD--
thisDD = DD < 10 ? '0' + DD : DD
}
}
// 获取达到条件的日期是星期X
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week')
// 当星期日时
if (thisWeek === 1) {
// 先找下一个日,并判断是否为月底
DD++;
thisDD = DD < 10 ? '0' + DD : DD;
DD++
thisDD = DD < 10 ? '0' + DD : DD
// 判断下一日已经不是合法日期
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD -= 3;
DD -= 3
}
} else if (thisWeek === 7) {
// 当星期6时只需判断不是1号就可进行操作
if (dayRuleSup.value !== 1) {
DD--;
DD--
} else {
DD += 2;
DD += 2
}
}
} else if (dayRule.value === 'weekDay') {
// 如果指定了是星期几
// 获取当前日期是属于星期几
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week')
// 校验当前星期是否在星期池dayRuleSup
if (dayRuleSup.value.indexOf(thisWeek) < 0) {
// 如果到达最大值时
if (Di === DDate.length - 1) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue goMonth;
continue goMonth
}
continue;
continue
}
} else if (dayRule.value === 'assWeek') {
// 如果指定了是第几周的星期几
// 获取每月1号是属于星期几
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week')
if (dayRuleSup.value[1] >= thisWeek) {
DD = (dayRuleSup.value[0] - 1) * 7 + dayRuleSup.value[1] - thisWeek + 1;
DD = (dayRuleSup.value[0] - 1) * 7 + dayRuleSup.value[1] - thisWeek + 1
} else {
DD = dayRuleSup.value[0] * 7 + dayRuleSup.value[1] - thisWeek + 1;
DD = dayRuleSup.value[0] * 7 + dayRuleSup.value[1] - thisWeek + 1
}
} else if (dayRule.value === 'lastWeek') {
// 如果指定了每月最后一个星期几
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
DD--
thisDD = DD < 10 ? '0' + DD : DD
}
}
// 获取月末最后一天是星期几
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week')
// 找到要求中最近的那个星期几
if (dayRuleSup.value < thisWeek) {
DD -= thisWeek - dayRuleSup.value;
DD -= thisWeek - dayRuleSup.value
} else if (dayRuleSup.value > thisWeek) {
DD -= 7 - (dayRuleSup.value - thisWeek)
}
}
// 判断时间值是否小于10置换成“05”这种格式
DD = DD < 10 ? '0' + DD : DD;
DD = DD < 10 ? '0' + DD : DD
// 循环“时”数组
goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
// 如果到达最大值时
if (nMin > mDate[mDate.length - 1]) {
resetMin();
resetMin()
if (hi === hDate.length - 1) {
resetHour();
resetHour()
if (Di === DDate.length - 1) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue goMonth;
continue goMonth
}
continue goDay;
continue goDay
}
continue;
continue
}
// 循环"分"数组
goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];
let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi]
// 如果到达最大值时
if (nSecond > sDate[sDate.length - 1]) {
resetSecond();
resetSecond()
if (mi === mDate.length - 1) {
resetMin();
resetMin()
if (hi === hDate.length - 1) {
resetHour();
resetHour()
if (Di === DDate.length - 1) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue goMonth;
continue goMonth
}
continue goDay;
continue goDay
}
continue goHour;
continue goHour
}
continue;
continue
}
// 循环"秒"数组
goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];
let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si]
// 添加当前时间(时间合法性在日期循环时已经判断)
if (MM !== '00' && DD !== '00') {
resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
nums++;
nums++
}
// 如果条数满了就退出循环
if (nums === 5) break goYear;
if (nums === 5) break goYear
// 如果到达最大值时
if (si === sDate.length - 1) {
resetSecond();
resetSecond()
if (mi === mDate.length - 1) {
resetMin();
resetMin()
if (hi === hDate.length - 1) {
resetHour();
resetHour()
if (Di === DDate.length - 1) {
resetDay();
resetDay()
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
resetMonth()
continue goYear
}
continue goMonth;
continue goMonth
}
continue goDay;
continue goDay
}
continue goHour;
continue goHour
}
continue goMin;
continue goMin
}
} //goSecond
} //goMin
@@ -318,31 +318,31 @@ function expressionChange() {
}
// 判断100年内的结果条数
if (resultArr.length === 0) {
resultList.value = ['没有达到条件的结果!'];
resultList.value = ['没有达到条件的结果!']
} else {
resultList.value = resultArr;
resultList.value = resultArr
if (resultArr.length !== 5) {
resultList.value.push('最近100年内只有上面' + resultArr.length + '条结果!')
}
}
// 计算完成-显示结果
isShow.value = true;
isShow.value = true
}
// 用于计算某位数字在数组中的索引
function getIndex(arr, value) {
if (value <= arr[0] || value > arr[arr.length - 1]) {
return 0;
return 0
} else {
for (let i = 0; i < arr.length - 1; i++) {
if (value > arr[i] && value <= arr[i + 1]) {
return i + 1;
return i + 1
}
}
}
}
// 获取"年"数组
function getYearArr(rule, year) {
dateArr.value[5] = getOrderArr(year, year + 100);
dateArr.value[5] = getOrderArr(year, year + 100)
if (rule !== undefined) {
if (rule.indexOf('-') >= 0) {
dateArr.value[5] = getCycleArr(rule, year + 100, false)
@@ -355,7 +355,7 @@ function getYearArr(rule, year) {
}
// 获取"月"数组
function getMonthArr(rule) {
dateArr.value[4] = getOrderArr(1, 12);
dateArr.value[4] = getOrderArr(1, 12)
if (rule.indexOf('-') >= 0) {
dateArr.value[4] = getCycleArr(rule, 12, false)
} else if (rule.indexOf('/') >= 0) {
@@ -369,58 +369,58 @@ function getWeekArr(rule) {
// 只有当日期规则的两个值均为“”时则表达日期是有选项的
if (dayRule.value === '' && dayRuleSup.value === '') {
if (rule.indexOf('-') >= 0) {
dayRule.value = 'weekDay';
dayRule.value = 'weekDay'
dayRuleSup.value = getCycleArr(rule, 7, false)
} else if (rule.indexOf('#') >= 0) {
dayRule.value = 'assWeek';
let matchRule = rule.match(/[0-9]{1}/g);
dayRuleSup.value = [Number(matchRule[1]), Number(matchRule[0])];
dateArr.value[3] = [1];
dayRule.value = 'assWeek'
let matchRule = rule.match(/[0-9]{1}/g)
dayRuleSup.value = [Number(matchRule[1]), Number(matchRule[0])]
dateArr.value[3] = [1]
if (dayRuleSup.value[1] === 7) {
dayRuleSup.value[1] = 0;
dayRuleSup.value[1] = 0
}
} else if (rule.indexOf('L') >= 0) {
dayRule.value = 'lastWeek';
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
dateArr.value[3] = [31];
dayRule.value = 'lastWeek'
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0])
dateArr.value[3] = [31]
if (dayRuleSup.value === 7) {
dayRuleSup.value = 0;
dayRuleSup.value = 0
}
} else if (rule !== '*' && rule !== '?') {
dayRule.value = 'weekDay';
dayRule.value = 'weekDay'
dayRuleSup.value = getAssignArr(rule)
}
}
}
// 获取"日"数组-少量为日期规则
function getDayArr(rule) {
dateArr.value[3] = getOrderArr(1, 31);
dayRule.value = '';
dayRuleSup.value = '';
dateArr.value[3] = getOrderArr(1, 31)
dayRule.value = ''
dayRuleSup.value = ''
if (rule.indexOf('-') >= 0) {
dateArr.value[3] = getCycleArr(rule, 31, false)
dayRuleSup.value = 'null';
dayRuleSup.value = 'null'
} else if (rule.indexOf('/') >= 0) {
dateArr.value[3] = getAverageArr(rule, 31)
dayRuleSup.value = 'null';
dayRuleSup.value = 'null'
} else if (rule.indexOf('W') >= 0) {
dayRule.value = 'workDay';
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
dateArr.value[3] = [dayRuleSup.value];
dayRule.value = 'workDay'
dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0])
dateArr.value[3] = [dayRuleSup.value]
} else if (rule.indexOf('L') >= 0) {
dayRule.value = 'lastDay';
dayRuleSup.value = 'null';
dateArr.value[3] = [31];
dayRule.value = 'lastDay'
dayRuleSup.value = 'null'
dateArr.value[3] = [31]
} else if (rule !== '*' && rule !== '?') {
dateArr.value[3] = getAssignArr(rule)
dayRuleSup.value = 'null';
dayRuleSup.value = 'null'
} else if (rule === '*') {
dayRuleSup.value = 'null';
dayRuleSup.value = 'null'
}
}
// 获取"时"数组
function getHourArr(rule) {
dateArr.value[2] = getOrderArr(0, 23);
dateArr.value[2] = getOrderArr(0, 23)
if (rule.indexOf('-') >= 0) {
dateArr.value[2] = getCycleArr(rule, 24, true)
} else if (rule.indexOf('/') >= 0) {
@@ -431,7 +431,7 @@ function getHourArr(rule) {
}
// 获取"分"数组
function getMinArr(rule) {
dateArr.value[1] = getOrderArr(0, 59);
dateArr.value[1] = getOrderArr(0, 59)
if (rule.indexOf('-') >= 0) {
dateArr.value[1] = getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
@@ -442,7 +442,7 @@ function getMinArr(rule) {
}
// 获取"秒"数组
function getSecondArr(rule) {
dateArr.value[0] = getOrderArr(0, 59);
dateArr.value[0] = getOrderArr(0, 59)
if (rule.indexOf('-') >= 0) {
dateArr.value[0] = getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
@@ -453,86 +453,86 @@ function getSecondArr(rule) {
}
// 根据传进来的min-max返回一个顺序的数组
function getOrderArr(min, max) {
let arr = [];
let arr = []
for (let i = min; i <= max; i++) {
arr.push(i);
arr.push(i)
}
return arr;
return arr
}
// 根据规则中指定的零散值返回一个数组
function getAssignArr(rule) {
let arr = [];
let assiginArr = rule.split(',');
let arr = []
let assiginArr = rule.split(',')
for (let i = 0; i < assiginArr.length; i++) {
arr[i] = Number(assiginArr[i])
}
arr.sort(compare)
return arr;
return arr
}
// 根据一定算术规则计算返回一个数组
function getAverageArr(rule, limit) {
let arr = [];
let agArr = rule.split('/');
let min = Number(agArr[0]);
let step = Number(agArr[1]);
let arr = []
let agArr = rule.split('/')
let min = Number(agArr[0])
let step = Number(agArr[1])
while (min <= limit) {
arr.push(min);
min += step;
arr.push(min)
min += step
}
return arr;
return arr
}
// 根据规则返回一个具有周期性的数组
function getCycleArr(rule, limit, status) {
// status--表示是否从0开始则从1开始
let arr = [];
let cycleArr = rule.split('-');
let min = Number(cycleArr[0]);
let max = Number(cycleArr[1]);
let arr = []
let cycleArr = rule.split('-')
let min = Number(cycleArr[0])
let max = Number(cycleArr[1])
if (min > max) {
max += limit;
max += limit
}
for (let i = min; i <= max; i++) {
let add = 0;
let add = 0
if (status === false && i % limit === 0) {
add = limit;
add = limit
}
arr.push(Math.round(i % limit + add))
}
arr.sort(compare)
return arr;
return arr
}
// 比较数字大小用于Array.sort
function compare(value1, value2) {
if (value2 - value1 > 0) {
return -1;
return -1
} else {
return 1;
return 1
}
}
// 格式化日期格式如2017-9-19 18:04:33
function formatDate(value, type) {
// 计算日期相关值
let time = typeof value == 'number' ? new Date(value) : value;
let Y = time.getFullYear();
let M = time.getMonth() + 1;
let D = time.getDate();
let h = time.getHours();
let m = time.getMinutes();
let s = time.getSeconds();
let week = time.getDay();
let time = typeof value == 'number' ? new Date(value) : value
let Y = time.getFullYear()
let M = time.getMonth() + 1
let D = time.getDate()
let h = time.getHours()
let m = time.getMinutes()
let s = time.getSeconds()
let week = time.getDay()
// 如果传递了type的话
if (type === undefined) {
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
} else if (type === 'week') {
// 在quartz中 1为星期日
return week + 1;
return week + 1
}
}
// 检查日期是否存在
function checkDate(value) {
let time = new Date(value);
let time = new Date(value)
let format = formatDate(time)
return value === format;
return value === format
}
onMounted(() => {
expressionChange()

View File

@@ -1,13 +1,13 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" />
@@ -15,7 +15,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始
<el-input-number v-model='average02' :min="1" :max="59 - average01" /> 秒执行一次
@@ -23,7 +23,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />

View File

@@ -1,19 +1,19 @@
<template>
<el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
<el-radio v-model='radioValue' :value="1">
允许的通配符[, - * ? / L #]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
<el-radio v-model='radioValue' :value="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-radio v-model='radioValue' :value="3">
周期从
<el-select clearable v-model="cycle01">
<el-option
@@ -38,7 +38,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-radio v-model='radioValue' :value="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的
<el-select clearable v-model="average02">
@@ -48,7 +48,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
<el-radio v-model='radioValue' :value="5">
本月最后一个
<el-select clearable v-model="weekday">
<el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
@@ -57,7 +57,7 @@
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
<el-radio v-model='radioValue' :value="6">
指定
<el-select class="multiselect" clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="6">
<el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />

View File

@@ -1,19 +1,19 @@
<template>
<el-form>
<el-form-item>
<el-radio :label="1" v-model='radioValue'>
<el-radio :value="1" v-model='radioValue'>
不填允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model='radioValue'>
<el-radio :value="2" v-model='radioValue'>
每年
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model='radioValue'>
<el-radio :value="3" v-model='radioValue'>
周期从
<el-input-number v-model='cycle01' :min='fullYear' :max="2098"/> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099"/>
@@ -21,7 +21,7 @@
</el-form-item>
<el-form-item>
<el-radio :label="4" v-model='radioValue'>
<el-radio :value="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始
<el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear"/> 年执行一次
@@ -30,7 +30,7 @@
</el-form-item>
<el-form-item>
<el-radio :label="5" v-model='radioValue'>
<el-radio :value="5" v-model='radioValue'>
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8">
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
@@ -61,22 +61,24 @@ const props = defineProps({
}
}
})
const fullYear = ref(0)
const maxFullYear = ref(0)
const fullYear = Number(new Date().getFullYear())
const maxFullYear = fullYear + 10
const radioValue = ref(1)
const cycle01 = ref(0)
const cycle02 = ref(0)
const average01 = ref(0)
const cycle01 = ref(fullYear)
const cycle02 = ref(fullYear + 1)
const average01 = ref(fullYear)
const average02 = ref(1)
const checkboxList = ref([])
const checkCopy = ref([])
const checkCopy = ref([fullYear])
const cycleTotal = computed(() => {
cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1)
cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value)
cycle01.value = props.check(cycle01.value, fullYear, maxFullYear - 1)
cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear)
return cycle01.value + '-' + cycle02.value
})
const averageTotal = computed(() => {
average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1)
average01.value = props.check(average01.value, fullYear, maxFullYear - 1)
average02.value = props.check(average02.value, 1, 10)
return average01.value + '/' + average02.value
})
@@ -97,8 +99,8 @@ function changeRadioValue(value) {
radioValue.value = 3
} else if (value.indexOf("/") > -1) {
const indexArr = value.split('/')
average01.value = Number(indexArr[1])
average02.value = Number(indexArr[0])
average01.value = Number(indexArr[0])
average02.value = Number(indexArr[1])
radioValue.value = 4
} else {
checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
@@ -129,14 +131,6 @@ function onRadioChange() {
break
}
}
onMounted(() => {
fullYear.value = Number(new Date().getFullYear())
maxFullYear.value = fullYear.value + 10
cycle01.value = fullYear.value
cycle02.value = cycle01.value + 1
average01.value = fullYear.value
checkCopy.value = [fullYear.value]
})
</script>
<style lang="scss" scoped>

View File

@@ -13,7 +13,7 @@
:disable-transitions="true"
:key="item.value + ''"
:index="index"
:type="item.elTagType === 'primary' ? '' : item.elTagType"
:type="item.elTagType"
:class="item.elTagClass"
>{{ item.label + " " }}</el-tag>
</template>
@@ -26,7 +26,7 @@
<script setup>
// 记录未匹配的项
const unmatchArray = ref([]);
const unmatchArray = ref([])
const props = defineProps({
// 数据
@@ -45,17 +45,17 @@ const props = defineProps({
type: String,
default: ",",
}
});
})
const values = computed(() => {
if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [];
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
});
if (props.value === null || typeof props.value === 'undefined' || props.value === '') return []
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator)
})
const unmatch = computed(() => {
unmatchArray.value = [];
unmatchArray.value = []
// 没有value不显示
if (props.value === null || typeof props.value === 'undefined' || props.value === '' || props.options.length === 0) return false
if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false
// 传入值为数组
let unmatch = false // 添加一个标志来判断是否有未匹配项
values.value.forEach(item => {
@@ -65,13 +65,13 @@ const unmatch = computed(() => {
}
})
return unmatch // 返回标志的值
});
})
function handleArray(array) {
if (array.length === 0) return "";
if (array.length === 0) return ""
return array.reduce((pre, cur) => {
return pre + " " + cur;
});
return pre + " " + cur
})
}
</script>

View File

@@ -27,17 +27,18 @@
</template>
<script setup>
import { QuillEditor } from "@vueup/vue-quill";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { getToken } from "@/utils/auth";
import axios from 'axios'
import { QuillEditor } from "@vueup/vue-quill"
import "@vueup/vue-quill/dist/vue-quill.snow.css"
import { getToken } from "@/utils/auth"
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance()
const quillEditorRef = ref();
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
const quillEditorRef = ref()
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // 上传的图片服务器地址
const headers = ref({
Authorization: "Bearer " + getToken()
});
})
const props = defineProps({
/* 编辑器的内容 */
@@ -69,7 +70,7 @@ const props = defineProps({
type: String,
default: "url",
}
});
})
const options = ref({
theme: "snow",
@@ -92,59 +93,60 @@ const options = ref({
},
placeholder: "请输入内容",
readOnly: props.readOnly
});
})
const styles = computed(() => {
let style = {};
let style = {}
if (props.minHeight) {
style.minHeight = `${props.minHeight}px`;
style.minHeight = `${props.minHeight}px`
}
if (props.height) {
style.height = `${props.height}px`;
style.height = `${props.height}px`
}
return style;
});
return style
})
const content = ref("");
const content = ref("")
watch(() => props.modelValue, (v) => {
if (v !== content.value) {
content.value = v === undefined ? "<p></p>" : v;
content.value = v == undefined ? "<p></p>" : v
}
}, { immediate: true });
}, { immediate: true })
// 如果设置了上传地址则自定义图片上传事件
onMounted(() => {
if (props.type == 'url') {
let quill = quillEditorRef.value.getQuill();
let toolbar = quill.getModule("toolbar");
let quill = quillEditorRef.value.getQuill()
let toolbar = quill.getModule("toolbar")
toolbar.addHandler("image", (value) => {
if (value) {
proxy.$refs.uploadRef.click();
proxy.$refs.uploadRef.click()
} else {
quill.format("image", false);
quill.format("image", false)
}
});
})
quill.root.addEventListener('paste', handlePasteCapture, true)
}
});
})
// 上传前校检格式和大小
function handleBeforeUpload(file) {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const isJPG = type.includes(file.type);
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"]
const isJPG = type.includes(file.type)
//检验文件格式
if (!isJPG) {
proxy.$modal.msgError(`图片格式错误!`);
return false;
proxy.$modal.msgError(`图片格式错误!`)
return false
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false
}
}
return true;
return true
}
// 上传成功处理
@@ -152,21 +154,44 @@ function handleUploadSuccess(res, file) {
// 如果上传成功
if (res.code == 200) {
// 获取富文本实例
let quill = toRaw(quillEditorRef.value).getQuill();
let quill = toRaw(quillEditorRef.value).getQuill()
// 获取光标位置
let length = quill.selection.savedRange.index;
let length = quill.selection.savedRange.index
// 插入图片res.url为服务器返回的图片链接地址
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName);
quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName)
// 调整光标到最后
quill.setSelection(length + 1);
quill.setSelection(length + 1)
} else {
proxy.$modal.msgError("图片插入失败");
proxy.$modal.msgError("图片插入失败")
}
}
// 上传失败处理
function handleUploadError() {
proxy.$modal.msgError("图片插入失败");
proxy.$modal.msgError("图片插入失败")
}
// 复制粘贴图片处理
function handlePasteCapture(e) {
const clipboard = e.clipboardData || window.clipboardData
if (clipboard && clipboard.items) {
for (let i = 0; i < clipboard.items.length; i++) {
const item = clipboard.items[i]
if (item.type.indexOf('image') !== -1) {
e.preventDefault()
const file = item.getAsFile()
insertImage(file)
}
}
}
}
function insertImage(file) {
const formData = new FormData()
formData.append("file", file)
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
handleUploadSuccess(res.data)
})
}
</script>

View File

@@ -5,6 +5,7 @@
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:data="data"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
@@ -13,25 +14,26 @@
:headers="headers"
class="upload-file-uploader"
ref="fileUpload"
v-if="!disabled"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
<div class="el-upload__tip" v-if="showTip && !disabled">
请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件
</div>
<!-- 文件列表 -->
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
<el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled">&nbsp;删除</el-link>
</div>
</li>
</transition-group>
@@ -39,126 +41,152 @@
</template>
<script setup>
import { getToken } from "@/utils/auth";
import { getToken } from "@/utils/auth"
import Sortable from 'sortablejs'
const props = defineProps({
modelValue: [String, Object, Array],
// 上传接口地址
action: {
type: String,
default: "/common/upload"
},
// 上传携带的参数
data: {
type: Object
},
// 数量限制
limit: {
type: Number,
default: 5,
default: 5
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 5,
default: 5
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["doc", "xls", "ppt", "txt", "pdf"],
default: () => ["doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"]
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
// 禁用组件(仅查看文件)
disabled: {
type: Boolean,
default: false
},
// 拖动排序
drag: {
type: Boolean,
default: true
}
});
})
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传文件服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const number = ref(0)
const uploadList = ref([])
const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传文件服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() })
const fileList = ref([])
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
)
watch(() => props.modelValue, val => {
if (val) {
let temp = 1;
let temp = 1
// 首先将值转为数组
const list = Array.isArray(val) ? val : props.modelValue.split(',');
const list = Array.isArray(val) ? val : props.modelValue.split(',')
// 然后将数组转为对象数组
fileList.value = list.map(item => {
if (typeof item === "string") {
item = { name: item, url: item };
item = { name: item, url: item }
}
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
item.uid = item.uid || new Date().getTime() + temp++
return item
})
} else {
fileList.value = [];
return [];
fileList.value = []
return []
}
},{ deep: true, immediate: true });
},{ deep: true, immediate: true })
// 上传前校检格式和大小
function handleBeforeUpload(file) {
// 校检文件类型
if (props.fileType.length) {
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1];
const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1]
const isTypeOk = props.fileType.indexOf(fileExt) >= 0
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
proxy.$modal.msgError(`文件格式不正确请上传${props.fileType.join("/")}格式文件!`)
return false
}
}
// 校检文件名是否包含特殊字符
if (file.name.includes(',')) {
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
return false
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false
}
}
proxy.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
proxy.$modal.loading("正在上传文件,请稍候...")
number.value++
return true
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`)
}
// 上传失败
function handleUploadError(err) {
proxy.$modal.msgError("上传文件失败");
proxy.$modal.msgError("上传文件失败")
proxy.$modal.closeLoading()
}
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName });
uploadedSuccessfully();
uploadList.value.push({ name: res.fileName, url: res.fileName })
uploadedSuccessfully()
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.fileUpload.handleRemove(file);
uploadedSuccessfully();
number.value--
proxy.$modal.closeLoading()
proxy.$modal.msgError(res.msg)
proxy.$refs.fileUpload.handleRemove(file)
uploadedSuccessfully()
}
}
// 删除文件
function handleDelete(index) {
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
fileList.value.splice(index, 1)
emit("update:modelValue", listToString(fileList.value))
}
// 上传结束处理
function uploadedSuccessfully() {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = []
number.value = 0
emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading()
}
}
@@ -166,26 +194,46 @@ function uploadedSuccessfully() {
function getFileName(name) {
// 如果是url那么取最后的名字 如果不是直接返回
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
return name.slice(name.lastIndexOf("/") + 1)
} else {
return name;
return name
}
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
let strs = "";
separator = separator || ",";
let strs = ""
separator = separator || ","
for (let i in list) {
if (list[i].url) {
strs += list[i].url + separator;
strs += list[i].url + separator
}
}
return strs != '' ? strs.substr(0, strs.length - 1) : '';
return strs != '' ? strs.substr(0, strs.length - 1) : ''
}
</script>
// 初始化拖拽排序
onMounted(() => {
if (props.drag && !props.disabled) {
nextTick(() => {
const element = proxy.$refs.uploadFileList?.$el || proxy.$refs.uploadFileList
Sortable.create(element, {
ghostClass: 'file-upload-darg',
onEnd: (evt) => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value))
}
})
})
}
})
</script>
<style scoped lang="scss">
.file-upload-darg {
opacity: 0.5;
background: #c8ebfb;
}
.upload-file-uploader {
margin-bottom: 5px;
}
@@ -194,6 +242,7 @@ function listToString(list, separator) {
line-height: 2;
margin-bottom: 10px;
position: relative;
transition: none !important;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;

View File

@@ -7,6 +7,7 @@
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
fill="currentColor"
>
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
@@ -23,7 +24,7 @@ defineProps({
const emit = defineEmits()
const toggleClick = () => {
emit('toggleClick');
emit('toggleClick')
}
</script>

View File

@@ -1,19 +1,46 @@
<template>
<div :class="{ 'show': show }" class="header-search">
<div class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelectRef"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
<el-dialog
v-model="show"
width="600"
@close="close"
:show-close="false"
append-to-body
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
<el-input
v-model="search"
ref="headerSearchSelectRef"
size="large"
@input="querySearch"
prefix-icon="Search"
placeholder="菜单搜索支持标题、URL模糊查询"
clearable
@keyup.enter="selectActiveResult"
@keydown.up.prevent="navigateResult('up')"
@keydown.down.prevent="navigateResult('down')"
>
</el-input>
<div class="result-wrap">
<el-scrollbar>
<div class="search-item" tabindex="1" v-for="(item, index) in options" :key="item.path" :style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1">
<div class="left">
<svg-icon class="menu-icon" :icon-class="item.icon" />
</div>
<div class="search-info" @click="change(item)">
<div class="menu-title">
{{ item.title.join(" / ") }}
</div>
<div class="menu-path">
{{ item.path }}
</div>
</div>
<svg-icon icon-class="enter" v-show="index === activeIndex"/>
</div>
</el-scrollbar>
</div>
</el-dialog>
</div>
</template>
@@ -21,38 +48,46 @@
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const search = ref('');
const options = ref([]);
const searchPool = ref([]);
const show = ref(false);
const fuse = ref(undefined);
const headerSearchSelectRef = ref(null);
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const search = ref('')
const options = ref([])
const searchPool = ref([])
const activeIndex = ref(-1)
const show = ref(false)
const fuse = ref(undefined)
const headerSearchSelectRef = ref(null)
const router = useRouter()
const theme = computed(() => useSettingsStore().theme)
const routes = computed(() => usePermissionStore().defaultRoutes)
function click() {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
options.value = searchPool.value
}
};
}
function close() {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
search.value = ''
options.value = []
show.value = false
activeIndex.value = -1
}
function change(val) {
const path = val.path;
const query = val.query;
const path = val.path
const query = val.query
if (isHttp(path)) {
// http(s):// 路径新窗口打开
const pindex = path.indexOf("http");
window.open(path.substr(pindex, path.length), "_blank");
const pindex = path.indexOf("http")
window.open(path.substr(pindex, path.length), "_blank")
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) });
router.push({ path: path, query: JSON.parse(query) })
} else {
router.push(path)
}
@@ -64,6 +99,7 @@ function change(val) {
show.value = false
})
}
function initFuse(list) {
fuse.value = new Fuse(list, {
shouldSort: true,
@@ -80,6 +116,7 @@ function initFuse(list) {
}]
})
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
function generateRoutes(routes, basePath = '', prefixTitle = []) {
@@ -88,16 +125,17 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
for (const r of routes) {
// skip hidden router
if (r.hidden) { continue }
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
title: [...prefixTitle],
icon: ''
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title]
if (r.redirect !== 'noRedirect') {
data.icon = r.meta.icon
if (r.redirect !== "noRedirect") {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
@@ -117,30 +155,42 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
}
return res
}
function querySearch(query) {
activeIndex.value = -1
if (query !== '') {
options.value = fuse.value.search(query)
options.value = fuse.value.search(query).map((item) => item.item) ?? searchPool.value
} else {
options.value = []
options.value = searchPool.value
}
}
function activeStyle(index) {
if (index !== activeIndex.value) return {}
return {
"background-color": theme.value,
"color": "#fff"
}
}
function navigateResult(direction) {
if (direction === "up") {
activeIndex.value = activeIndex.value <= 0 ? options.value.length - 1 : activeIndex.value - 1
} else if (direction === "down") {
activeIndex.value = activeIndex.value >= options.value.length - 1 ? 0 : activeIndex.value + 1
}
}
function selectActiveResult() {
if (options.value.length > 0 && activeIndex.value >= 0) {
change(options.value[activeIndex.value])
}
}
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
searchPool.value = generateRoutes(routes.value)
})
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
})
watch(searchPool, (list) => {
initFuse(list)
})
@@ -148,40 +198,55 @@ watch(searchPool, (list) => {
<style lang='scss' scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
.result-wrap {
height: 280px;
margin: 6px 0;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
.search-item {
display: flex;
height: 48px;
align-items: center;
padding-right: 10px;
.left {
width: 60px;
text-align: center;
.menu-icon {
width: 18px;
height: 18px;
}
}
.search-info {
padding-left: 5px;
margin-top: 10px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex: 1;
.menu-title,
.menu-path {
height: 20px;
}
.menu-path {
color: #ccc;
font-size: 10px;
}
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
.search-item:hover {
cursor: pointer;
}
}
</style>
</style>

View File

@@ -30,11 +30,11 @@ const props = defineProps({
activeIcon: {
type: String
}
});
})
const iconName = ref('');
const iconList = ref(icons);
const emit = defineEmits(['selected']);
const iconName = ref('')
const iconList = ref(icons)
const emit = defineEmits(['selected'])
function filterIcons() {
iconList.value = icons

View File

@@ -1,8 +1,8 @@
let icons = []
const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
const modules = import.meta.glob('./../../assets/icons/svg/*.svg')
for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
icons.push(p);
const p = path.split('assets/icons/svg/')[1].split('.svg')[0]
icons.push(p)
}
export default icons

View File

@@ -15,7 +15,7 @@
</template>
<script setup>
import { isExternal } from "@/utils/validate";
import { isExternal } from "@/utils/validate"
const props = defineProps({
src: {
@@ -30,41 +30,41 @@ const props = defineProps({
type: [Number, String],
default: ""
}
});
})
const realSrc = computed(() => {
if (!props.src) {
return;
return
}
let real_src = props.src.split(",")[0];
let real_src = props.src.split(",")[0]
if (isExternal(real_src)) {
return real_src;
return real_src
}
return import.meta.env.VITE_APP_BASE_API + real_src;
});
return import.meta.env.VITE_APP_BASE_API + real_src
})
const realSrcList = computed(() => {
if (!props.src) {
return;
return
}
let real_src_list = props.src.split(",");
let srcList = [];
let real_src_list = props.src.split(",")
let srcList = []
real_src_list.forEach(item => {
if (isExternal(item)) {
return srcList.push(item);
return srcList.push(item)
}
return srcList.push(import.meta.env.VITE_APP_BASE_API + item);
});
return srcList;
});
return srcList.push(import.meta.env.VITE_APP_BASE_API + item)
})
return srcList
})
const realWidth = computed(() =>
typeof props.width == "string" ? props.width : `${props.width}px`
);
)
const realHeight = computed(() =>
typeof props.height == "string" ? props.height : `${props.height}px`
);
)
</script>
<style lang="scss" scoped>

View File

@@ -2,10 +2,12 @@
<div class="component-upload-image">
<el-upload
multiple
:disabled="disabled"
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:data="data"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
@@ -20,7 +22,7 @@
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
<div class="el-upload__tip" v-if="showTip && !disabled">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
@@ -46,163 +48,202 @@
</template>
<script setup>
import { getToken } from "@/utils/auth";
import { getToken } from "@/utils/auth"
import { isExternal } from "@/utils/validate"
import Sortable from 'sortablejs'
const props = defineProps({
modelValue: [String, Object, Array],
// 上传接口地址
action: {
type: String,
default: "/common/upload"
},
// 上传携带的参数
data: {
type: Object
},
// 图片数量限制
limit: {
type: Number,
default: 5,
default: 5
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 5,
default: 5
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"],
default: () => ["png", "jpg", "jpeg"]
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
});
// 禁用组件(仅查看图片)
disabled: {
type: Boolean,
default: false
},
// 拖动排序
drag: {
type: Boolean,
default: true
}
})
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const { proxy } = getCurrentInstance()
const emit = defineEmits()
const number = ref(0)
const uploadList = ref([])
const dialogImageUrl = ref("")
const dialogVisible = ref(false)
const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() })
const fileList = ref([])
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
)
watch(() => props.modelValue, val => {
if (val) {
// 首先将值转为数组
const list = Array.isArray(val) ? val : props.modelValue.split(",");
const list = Array.isArray(val) ? val : props.modelValue.split(",")
// 然后将数组转为对象数组
fileList.value = list.map(item => {
if (typeof item === "string") {
if (item.indexOf(baseUrl) === -1) {
item = { name: baseUrl + item, url: baseUrl + item };
if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
item = { name: baseUrl + item, url: baseUrl + item }
} else {
item = { name: item, url: item };
item = { name: item, url: item }
}
}
return item;
});
return item
})
} else {
fileList.value = [];
return [];
fileList.value = []
return []
}
},{ deep: true, immediate: true });
},{ deep: true, immediate: true })
// 上传前loading加载
function handleBeforeUpload(file) {
let isImg = false;
let isImg = false
if (props.fileType.length) {
let fileExtension = "";
let fileExtension = ""
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1)
}
isImg = props.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
if (file.type.indexOf(type) > -1) return true
if (fileExtension && fileExtension.indexOf(type) > -1) return true
return false
})
} else {
isImg = file.type.indexOf("image") > -1;
isImg = file.type.indexOf("image") > -1
}
if (!isImg) {
proxy.$modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
);
return false;
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`)
return false
}
if (file.name.includes(',')) {
proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
return false
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
const isLt = file.size / 1024 / 1024 < props.fileSize
if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`)
return false
}
}
proxy.$modal.loading("正在上传图片,请稍候...");
number.value++;
proxy.$modal.loading("正在上传图片,请稍候...")
number.value++
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`)
}
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName });
uploadedSuccessfully();
uploadList.value.push({ name: res.fileName, url: res.fileName })
uploadedSuccessfully()
} else {
number.value--;
proxy.$modal.closeLoading();
proxy.$modal.msgError(res.msg);
proxy.$refs.imageUpload.handleRemove(file);
uploadedSuccessfully();
number.value--
proxy.$modal.closeLoading()
proxy.$modal.msgError(res.msg)
proxy.$refs.imageUpload.handleRemove(file)
uploadedSuccessfully()
}
}
// 删除图片
function handleDelete(file) {
const findex = fileList.value.map(f => f.name).indexOf(file.name);
const findex = fileList.value.map(f => f.name).indexOf(file.name)
if (findex > -1 && uploadList.value.length === number.value) {
fileList.value.splice(findex, 1);
emit("update:modelValue", listToString(fileList.value));
return false;
fileList.value.splice(findex, 1)
emit("update:modelValue", listToString(fileList.value))
return false
}
}
// 上传结束处理
function uploadedSuccessfully() {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy.$modal.closeLoading();
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = []
number.value = 0
emit("update:modelValue", listToString(fileList.value))
proxy.$modal.closeLoading()
}
}
// 上传失败
function handleUploadError() {
proxy.$modal.msgError("上传图片失败");
proxy.$modal.closeLoading();
proxy.$modal.msgError("上传图片失败")
proxy.$modal.closeLoading()
}
// 预览
function handlePictureCardPreview(file) {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
dialogImageUrl.value = file.url
dialogVisible.value = true
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
let strs = "";
separator = separator || ",";
let strs = ""
separator = separator || ","
for (let i in list) {
if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].url.replace(baseUrl, "") + separator;
strs += list[i].url.replace(baseUrl, "") + separator
}
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
return strs != "" ? strs.substr(0, strs.length - 1) : ""
}
// 初始化拖拽排序
onMounted(() => {
if (props.drag && !props.disabled) {
nextTick(() => {
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, {
onEnd: (evt) => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value))
}
})
})
}
})
</script>
<style scoped lang="scss">
@@ -210,4 +251,8 @@ function listToString(list, separator) {
:deep(.hide .el-upload--picture-card) {
display: none;
}
:deep(.el-upload.el-upload--picture-card.is-disabled) {
display: none !important;
}
</style>

View File

@@ -59,7 +59,7 @@ const props = defineProps({
}
})
const emit = defineEmits();
const emit = defineEmits()
const currentPage = computed({
get() {
return props.page
@@ -76,6 +76,7 @@ const pageSize = computed({
emit('update:limit', val)
}
})
function handleSizeChange(val) {
if (currentPage.value * val > props.total) {
currentPage.value = 1
@@ -85,19 +86,18 @@ function handleSizeChange(val) {
scrollTo(0, 800)
}
}
function handleCurrentChange(val) {
emit('pagination', { page: val, limit: pageSize.value })
if (props.autoScroll) {
scrollTo(0, 800)
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;

View File

@@ -7,15 +7,20 @@
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" />
<template #dropdown>
<el-dropdown-menu>
<template v-for="item in columns" :key="item.key">
<!-- 全选/反选 按钮 -->
<el-dropdown-item>
<el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox>
</el-dropdown-item>
<div class="check-line"></div>
<template v-for="(item, key) in columns" :key="item.key">
<el-dropdown-item>
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
<el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" />
</el-dropdown-item>
</template>
</el-dropdown-menu>
@@ -27,7 +32,7 @@
<el-transfer
:titles="['显示', '隐藏']"
v-model="value"
:data="columns"
:data="transferData"
@change="dataChange"
></el-transfer>
</el-dialog>
@@ -39,83 +44,119 @@ const props = defineProps({
/* 是否显示检索条件 */
showSearch: {
type: Boolean,
default: true,
default: true
},
/* 显隐列信息 */
/* 显隐列信息(数组格式、对象格式) */
columns: {
type: Array,
type: [Array, Object],
default: () => ({})
},
/* 是否显示检索图标 */
search: {
type: Boolean,
default: true,
default: true
},
/* 显隐列类型transfer穿梭框、checkbox复选框 */
showColumnsType: {
type: String,
default: "checkbox",
default: "checkbox"
},
/* 右外边距 */
gutter: {
type: Number,
default: 10,
default: 10
},
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
const emits = defineEmits(['update:showSearch', 'queryTable'])
// 显隐数据
const value = ref([]);
const value = ref([])
// 弹出层标题
const title = ref("显示/隐藏");
const title = ref("显示/隐藏")
// 是否显示弹出层
const open = ref(false);
const open = ref(false)
const style = computed(() => {
const ret = {};
const ret = {}
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
ret.marginRight = `${props.gutter / 2}px`
}
return ret;
});
return ret
})
// 是否全选/半选 状态
const isChecked = computed({
get: () => Array.isArray(props.columns) ? props.columns.every(col => col.visible) : Object.values(props.columns).every((col) => col.visible),
set: () => {}
})
const isIndeterminate = computed(() => Array.isArray(props.columns) ? props.columns.some((col) => col.visible) && !isChecked.value : Object.values(props.columns).some((col) => col.visible) && !isChecked.value)
const transferData = computed(() => Array.isArray(props.columns) ? props.columns.map((item, index) => ({ key: index, label: item.label })) : Object.keys(props.columns).map((key, index) => ({ key: index, label: props.columns[key].label })))
// 搜索
function toggleSearch() {
emits("update:showSearch", !props.showSearch);
emits("update:showSearch", !props.showSearch)
}
// 刷新
function refresh() {
emits("queryTable");
emits("queryTable")
}
// 右侧列表元素变化
function dataChange(data) {
for (let item in props.columns) {
const key = props.columns[item].key;
props.columns[item].visible = !data.includes(key);
if (Array.isArray(props.columns)) {
for (let item in props.columns) {
const key = props.columns[item].key
props.columns[item].visible = !data.includes(key)
}
} else {
Object.keys(props.columns).forEach((key, index) => {
props.columns[key].visible = !data.includes(index)
})
}
}
// 打开显隐列dialog
function showColumn() {
open.value = true;
open.value = true
}
if (props.showColumnsType == 'transfer') {
// 显隐列初始默认隐藏列
for (let item in props.columns) {
if (props.columns[item].visible === false) {
value.value.push(parseInt(item));
if (props.showColumnsType == "transfer") {
// transfer穿梭显隐列初始默认隐藏列
if (Array.isArray(props.columns)) {
for (let item in props.columns) {
if (props.columns[item].visible === false) {
value.value.push(parseInt(item))
}
}
} else {
Object.keys(props.columns).forEach((key, index) => {
if (props.columns[key].visible === false) {
value.value.push(index)
}
})
}
}
// 勾选
function checkboxChange(event, label) {
props.columns.filter(item => item.label == label)[0].visible = event;
// 勾选
function checkboxChange(event, key) {
if (Array.isArray(props.columns)) {
props.columns.filter(item => item.key == key)[0].visible = event
} else {
props.columns[key].visible = event
}
}
// 切换全选/反选
function toggleCheckAll() {
const newValue = !isChecked.value
if (Array.isArray(props.columns)) {
props.columns.forEach((col) => (col.visible = newValue))
} else {
Object.values(props.columns).forEach((col) => (col.visible = newValue))
}
}
</script>
<style lang='scss' scoped>
@@ -131,4 +172,10 @@ function checkboxChange(event, label) {
line-height: 30px;
padding: 0 17px;
}
.check-line {
width: 90%;
height: 1px;
background-color: #ccc;
margin: 3px auto;
}
</style>

View File

@@ -5,7 +5,7 @@
</template>
<script setup>
const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
const url = ref('http://doc.ruoyi.vip/ruoyi-vue')
function goto() {
window.open(url.value)

View File

@@ -5,7 +5,7 @@
</template>
<script setup>
const url = ref('https://gitee.com/y_project/RuoYi-Vue');
const url = ref('https://gitee.com/y_project/RuoYi-Vue')
function goto() {
window.open(url.value)

View File

@@ -7,7 +7,7 @@
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen();
const { isFullscreen, enter, exit, toggle } = useFullscreen()
</script>
<style lang='scss' scoped>

View File

@@ -16,23 +16,23 @@
</template>
<script setup>
import useAppStore from "@/store/modules/app";
import useAppStore from "@/store/modules/app"
const appStore = useAppStore();
const size = computed(() => appStore.size);
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
const appStore = useAppStore()
const size = computed(() => appStore.size)
const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance()
const sizeOptions = ref([
{ label: "较大", value: "large" },
{ label: "默认", value: "default" },
{ label: "稍小", value: "small" },
]);
])
function handleSetSize(size) {
proxy.$modal.loading("正在设置布局大小,请稍候...");
appStore.setSize(size);
setTimeout("window.location.reload()", 1000);
proxy.$modal.loading("正在设置布局大小,请稍候...")
appStore.setSize(size)
setTimeout("window.location.reload()", 1000)
}
</script>

View File

@@ -1,10 +1,10 @@
import * as components from '@element-plus/icons-vue'
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};
install: (app) => {
for (const key in components) {
const componentConfig = components[key]
app.component(componentConfig.name, componentConfig)
}
}
}

View File

@@ -40,126 +40,127 @@ import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
// 顶部栏初始数
const visibleNumber = ref(null);
const visibleNumber = ref(null)
// 当前激活菜单的 index
const currentIndex = ref(null);
const currentIndex = ref(null)
// 隐藏侧边栏路由
const hideList = ['/index', '/user/profile'];
const hideList = ['/index', '/user/profile']
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const route = useRoute();
const router = useRouter();
const route = useRoute()
const router = useRouter()
// 主题颜色
const theme = computed(() => settingsStore.theme);
const theme = computed(() => settingsStore.theme)
// 所有的路由信息
const routers = computed(() => permissionStore.topbarRouters);
const routers = computed(() => permissionStore.topbarRouters)
// 顶部显示菜单
const topMenus = computed(() => {
let topMenus = [];
let topMenus = []
routers.value.map((menu) => {
if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转
if (menu.path === "/") {
topMenus.push(menu.children[0]);
if (menu.path === '/' && menu.children) {
topMenus.push(menu.children[0])
} else {
topMenus.push(menu);
topMenus.push(menu)
}
}
})
return topMenus;
return topMenus
})
// 设置子路由
const childrenMenus = computed(() => {
let childrenMenus = [];
let childrenMenus = []
routers.value.map((router) => {
for (let item in router.children) {
if (router.children[item].parentPath === undefined) {
if(router.path === "/") {
router.children[item].path = "/" + router.children[item].path;
router.children[item].path = "/" + router.children[item].path
} else {
if(!isHttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path;
router.children[item].path = router.path + "/" + router.children[item].path
}
}
router.children[item].parentPath = router.path;
router.children[item].parentPath = router.path
}
childrenMenus.push(router.children[item]);
childrenMenus.push(router.children[item])
}
})
return constantRoutes.concat(childrenMenus);
return constantRoutes.concat(childrenMenus)
})
// 默认激活的菜单
const activeMenu = computed(() => {
const path = route.path;
let activePath = path;
const path = route.path
let activePath = path
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
const tmpPath = path.substring(1, path.length);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
const tmpPath = path.substring(1, path.length)
if (!route.meta.link) {
appStore.toggleSideBarHide(false);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"))
appStore.toggleSideBarHide(false)
}
} else if(!route.children) {
activePath = path;
appStore.toggleSideBarHide(true);
activePath = path
appStore.toggleSideBarHide(true)
}
activeRoutes(activePath);
return activePath;
activeRoutes(activePath)
return activePath
})
function setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3;
visibleNumber.value = parseInt(width / 85);
const width = document.body.getBoundingClientRect().width / 3
visibleNumber.value = parseInt(width / 85)
}
function handleSelect(key, keyPath) {
currentIndex.value = key;
const route = routers.value.find(item => item.path === key);
currentIndex.value = key
const route = routers.value.find(item => item.path === key)
if (isHttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank");
window.open(key, "_blank")
} else if (!route || !route.children) {
// 没有子路由路径内部打开
const routeMenu = childrenMenus.value.find(item => item.path === key);
const routeMenu = childrenMenus.value.find(item => item.path === key)
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
router.push({ path: key, query: query });
let query = JSON.parse(routeMenu.query)
router.push({ path: key, query: query })
} else {
router.push({ path: key });
router.push({ path: key })
}
appStore.toggleSideBarHide(true);
appStore.toggleSideBarHide(true)
} else {
// 显示左侧联动菜单
activeRoutes(key);
appStore.toggleSideBarHide(false);
activeRoutes(key)
appStore.toggleSideBarHide(false)
}
}
function activeRoutes(key) {
let routes = [];
let routes = []
if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
routes.push(item);
routes.push(item)
}
});
})
}
if(routes.length > 0) {
permissionStore.setSidebarRouters(routes);
permissionStore.setSidebarRouters(routes)
} else {
appStore.toggleSideBarHide(true);
appStore.toggleSideBarHide(true)
}
return routes;
return routes
}
onMounted(() => {
window.addEventListener('resize', setVisibleNumber)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber)
})
@@ -196,7 +197,7 @@ onMounted(() => {
/* 背景色隐藏 */
.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
background-color: #ffffff !important;
background-color: #ffffff;
}
/* 图标右间距 */
@@ -211,4 +212,6 @@ onMounted(() => {
margin-left: 8px;
margin-top: 0px;
}
</style>

View File

@@ -1,156 +0,0 @@
<template>
<div class="el-tree-select">
<el-select
style="width: 100%"
v-model="valueId"
ref="treeSelect"
:filterable="true"
:clearable="true"
@clear="clearHandle"
:filter-method="selectFilterData"
:placeholder="placeholder"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="objMap"
:node-key="objMap.value"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const props = defineProps({
/* 配置项 */
objMap: {
type: Object,
default: () => {
return {
value: 'id', // ID字段名
label: 'label', // 显示名称
children: 'children' // 子级字段名
}
}
},
/* 自动收起 */
accordion: {
type: Boolean,
default: () => {
return false
}
},
/**当前双向数据绑定的值 */
value: {
type: [String, Number],
default: ''
},
/**当前的数据 */
options: {
type: Array,
default: () => []
},
/**输入框内部的文字 */
placeholder: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:value']);
const valueId = computed({
get: () => props.value,
set: (val) => {
emit('update:value', val)
}
});
const valueTitle = ref('');
const defaultExpandedKey = ref([]);
function initHandle() {
nextTick(() => {
const selectedValue = valueId.value;
if(selectedValue !== null && typeof (selectedValue) !== 'undefined') {
const node = proxy.$refs.selectTree.getNode(selectedValue)
if (node) {
valueTitle.value = node.data[props.objMap.label]
proxy.$refs.selectTree.setCurrentKey(selectedValue) // 设置默认选中
defaultExpandedKey.value = [selectedValue] // 设置默认展开
}
} else {
clearHandle()
}
})
}
function handleNodeClick(node) {
valueTitle.value = node[props.objMap.label]
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
proxy.$refs.treeSelect.blur()
selectFilterData('')
}
function selectFilterData(val) {
proxy.$refs.selectTree.filter(val)
}
function filterNode(value, data) {
if (!value) return true
return data[props.objMap['label']].indexOf(value) !== -1
}
function clearHandle() {
valueTitle.value = ''
valueId.value = ''
defaultExpandedKey.value = [];
clearSelected()
}
function clearSelected() {
const allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
}
onMounted(() => {
initHandle()
})
watch(valueId, () => {
initHandle();
})
</script>
<style lang='scss' scoped>
@import "@/assets/styles/variables.module.scss";
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
padding: 0;
background-color: #fff;
height: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
box-sizing: border-box;
}
:deep(.el-tree-node__content:hover),
:deep(.el-tree-node__content:active),
:deep(.is-current > div:first-child),
:deep(.el-tree-node__content:focus) {
background-color: mix(#fff, $--color-primary, 90%);
color: $--color-primary;
}
</style>

View File

@@ -22,10 +22,10 @@ const url = computed(() => props.src)
onMounted(() => {
setTimeout(() => {
loading.value = false;
}, 300);
loading.value = false
}, 300)
window.onresize = function temp() {
height.value = document.documentElement.clientHeight - 94.5 + "px;";
};
height.value = document.documentElement.clientHeight - 94.5 + "px;"
}
})
</script>

View File

@@ -2,65 +2,64 @@
* v-copyText 复制文本内容
* Copyright (c) 2022 ruoyi
*/
export default {
beforeMount(el, { value, arg }) {
if (arg === "callback") {
el.$copyCallback = value;
el.$copyCallback = value
} else {
el.$copyValue = value;
el.$copyValue = value
const handler = () => {
copyTextToClipboard(el.$copyValue);
copyTextToClipboard(el.$copyValue)
if (el.$copyCallback) {
el.$copyCallback(el.$copyValue);
el.$copyCallback(el.$copyValue)
}
};
el.addEventListener("click", handler);
el.$destroyCopy = () => el.removeEventListener("click", handler);
}
el.addEventListener("click", handler)
el.$destroyCopy = () => el.removeEventListener("click", handler)
}
}
}
function copyTextToClipboard(input, { target = document.body } = {}) {
const element = document.createElement('textarea');
const previouslyFocusedElement = document.activeElement;
const element = document.createElement('textarea')
const previouslyFocusedElement = document.activeElement
element.value = input;
element.value = input
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '');
element.setAttribute('readonly', '')
element.style.contain = 'strict';
element.style.position = 'absolute';
element.style.left = '-9999px';
element.style.fontSize = '12pt'; // Prevent zooming on iOS
element.style.contain = 'strict'
element.style.position = 'absolute'
element.style.left = '-9999px'
element.style.fontSize = '12pt' // Prevent zooming on iOS
const selection = document.getSelection();
const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
const selection = document.getSelection()
const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0)
target.append(element);
element.select();
target.append(element)
element.select()
// Explicit selection workaround for iOS
element.selectionStart = 0;
element.selectionEnd = input.length;
element.selectionStart = 0
element.selectionEnd = input.length
let isSuccess = false;
let isSuccess = false
try {
isSuccess = document.execCommand('copy');
isSuccess = document.execCommand('copy')
} catch { }
element.remove();
element.remove()
if (originalRange) {
selection.removeAllRanges();
selection.addRange(originalRange);
selection.removeAllRanges()
selection.addRange(originalRange)
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
previouslyFocusedElement.focus();
previouslyFocusedElement.focus()
}
return isSuccess;
return isSuccess
}

View File

@@ -2,13 +2,12 @@
* v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi
*/
import useUserStore from '@/store/modules/user'
export default {
mounted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*";
const all_permission = "*:*:*"
const permissions = useUserStore().permissions
if (value && value instanceof Array && value.length > 0) {

View File

@@ -2,13 +2,12 @@
* v-hasRole 角色权限处理
* Copyright (c) 2019 ruoyi
*/
import useUserStore from '@/store/modules/user'
export default {
mounted(el, binding, vnode) {
const { value } = binding
const super_admin = "admin";
const super_admin = "admin"
const roles = useUserStore().roles
if (value && value instanceof Array && value.length > 0) {

View File

@@ -8,14 +8,31 @@
</transition>
</router-view>
<iframe-toggle />
<copyright />
</section>
</template>
<script setup>
import copyright from "./Copyright/index"
import iframeToggle from "./IframeToggle/index"
import useTagsViewStore from '@/store/modules/tagsView'
const route = useRoute()
const tagsViewStore = useTagsViewStore()
onMounted(() => {
addIframe()
})
watchEffect(() => {
addIframe()
})
function addIframe() {
if (route.meta.link) {
useTagsViewStore().addIframeView(route)
}
}
</script>
<style lang="scss" scoped>
@@ -28,7 +45,18 @@ const tagsViewStore = useTagsViewStore()
}
.fixed-header + .app-main {
padding-top: 50px;
overflow-y: auto;
scrollbar-gutter: auto;
height: calc(100vh - 50px);
min-height: 0px;
}
.app-main:has(.copyright) {
padding-bottom: 36px;
}
.fixed-header + .app-main {
margin-top: 50px;
}
.hasTagsView {
@@ -38,19 +66,14 @@ const tagsViewStore = useTagsViewStore()
}
.fixed-header + .app-main {
padding-top: 84px;
margin-top: 84px;
height: calc(100vh - 84px);
min-height: 0px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 6px;
}
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
@@ -65,4 +88,3 @@ const tagsViewStore = useTagsViewStore()
border-radius: 3px;
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<footer v-if="visible" class="copyright">
<span>{{ content }}</span>
</footer>
</template>
<script setup>
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const visible = computed(() => settingsStore.footerVisible)
const content = computed(() => settingsStore.footerContent)
</script>
<style scoped>
.copyright {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 36px;
padding: 10px 20px;
text-align: right;
background-color: #f8f8f8;
color: #666;
font-size: 14px;
border-top: 1px solid #e7e7e7;
z-index: 999;
}
</style>

View File

@@ -9,17 +9,17 @@
</template>
<script setup>
import InnerLink from "../InnerLink/index";
import useTagsViewStore from "@/store/modules/tagsView";
import InnerLink from "../InnerLink/index"
import useTagsViewStore from "@/store/modules/tagsView"
const route = useRoute();
const tagsViewStore = useTagsViewStore();
const route = useRoute()
const tagsViewStore = useTagsViewStore()
function iframeUrl(url, query) {
if (Object.keys(query).length > 0) {
let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
return url + "?" + params;
let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&")
return url + "?" + params
}
return url;
return url
}
</script>

View File

@@ -1,9 +1,10 @@
<template>
<div :style="'height:' + height">
<div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!">
<iframe
:id="iframeId"
style="width: 100%; height: 100%"
:src="src"
ref="iframeRef"
frameborder="no"
></iframe>
</div>
@@ -18,7 +19,17 @@ const props = defineProps({
iframeId: {
type: String
}
});
})
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
const loading = ref(true)
const height = ref(document.documentElement.clientHeight - 94.5 + 'px')
const iframeRef = ref(null)
onMounted(() => {
if (iframeRef.value) {
iframeRef.value.onload = () => {
loading.value = false
}
}
})
</script>

View File

@@ -1,8 +1,8 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
<div class="right-menu">
<template v-if="appStore.device !== 'mobile'">
@@ -18,31 +18,37 @@
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="主题模式" effect="dark" placement="bottom">
<div class="right-menu-item hover-effect theme-switch-wrapper" @click="toggleTheme">
<svg-icon v-if="settingsStore.isDark" icon-class="sunny" />
<svg-icon v-if="!settingsStore.isDark" icon-class="moon" />
</div>
</el-tooltip>
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<el-dropdown @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<span class="user-nickname"> {{ userStore.nickName }} </span>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
@@ -72,13 +78,13 @@ function toggleSideBar() {
function handleCommand(command) {
switch (command) {
case "setLayout":
setLayout();
break;
setLayout()
break
case "logout":
logout();
break;
logout()
break
default:
break;
break
}
}
@@ -89,14 +95,18 @@ function logout() {
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index';
location.href = '/index'
})
}).catch(() => { });
}).catch(() => { })
}
const emits = defineEmits(['setLayout'])
function setLayout() {
emits('setLayout');
emits('setLayout')
}
function toggleTheme() {
settingsStore.toggleTheme()
}
</script>
@@ -105,7 +115,7 @@ function setLayout() {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
background: var(--navbar-bg);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container {
@@ -161,20 +171,44 @@ function setLayout() {
background: rgba(0, 0, 0, 0.025);
}
}
&.theme-switch-wrapper {
display: flex;
align-items: center;
svg {
transition: transform 0.3s;
&:hover {
transform: scale(1.15);
}
}
}
}
.avatar-container {
margin-right: 40px;
margin-right: 0px;
padding-right: 0px;
.avatar-wrapper {
margin-top: 5px;
margin-top: 10px;
right: 8px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
width: 30px;
height: 30px;
margin-right: 8px;
border-radius: 50%;
}
.user-nickname{
position: relative;
left: 0px;
bottom: 10px;
font-size: 14px;
font-weight: bold;
}
i {

View File

@@ -1,5 +1,5 @@
<template>
<el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px">
<el-drawer v-model="showSettings" :withHeader="false" :lock-scroll="false" direction="rtl" size="300px">
<div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3>
</div>
@@ -49,6 +49,13 @@
</span>
</div>
<div class="drawer-item">
<span>显示页签图标</span>
<span class="comp-style">
<el-switch v-model="settingsStore.tagsIcon" :disabled="!settingsStore.tagsView" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>固定 Header</span>
<span class="comp-style">
@@ -66,7 +73,14 @@
<div class="drawer-item">
<span>动态标题</span>
<span class="comp-style">
<el-switch v-model="settingsStore.dynamicTitle" class="drawer-switch" />
<el-switch v-model="settingsStore.dynamicTitle" @change="dynamicTitleChange" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>底部版权</span>
<span class="comp-style">
<el-switch v-model="settingsStore.footerVisible" class="drawer-switch" />
</span>
</div>
@@ -79,79 +93,88 @@
</template>
<script setup>
import variables from '@/assets/styles/variables.module.scss'
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { useDynamicTitle } from '@/utils/dynamicTitle'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { handleThemeStyle } from '@/utils/theme'
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const showSettings = ref(false);
const theme = ref(settingsStore.theme);
const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
const showSettings = ref(false)
const theme = ref(settingsStore.theme)
const sideTheme = ref(settingsStore.sideTheme)
const storeSettings = computed(() => settingsStore)
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"])
/** 是否需要topnav */
function topNavChange(val) {
if (!val) {
appStore.toggleSideBarHide(false);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
appStore.toggleSideBarHide(false)
permissionStore.setSidebarRouters(permissionStore.defaultRoutes)
}
}
/** 是否需要dynamicTitle */
function dynamicTitleChange() {
useSettingsStore().setTitle(useSettingsStore().title)
}
function themeChange(val) {
settingsStore.theme = val;
handleThemeStyle(val);
settingsStore.theme = val
handleThemeStyle(val)
}
function handleTheme(val) {
settingsStore.sideTheme = val;
sideTheme.value = val;
settingsStore.sideTheme = val
sideTheme.value = val
}
function saveSetting() {
proxy.$modal.loading("正在保存到本地,请稍候...");
proxy.$modal.loading("正在保存到本地,请稍候...")
let layoutSetting = {
"topNav": storeSettings.value.topNav,
"tagsView": storeSettings.value.tagsView,
"tagsIcon": storeSettings.value.tagsIcon,
"fixedHeader": storeSettings.value.fixedHeader,
"sidebarLogo": storeSettings.value.sidebarLogo,
"dynamicTitle": storeSettings.value.dynamicTitle,
"footerVisible": storeSettings.value.footerVisible,
"sideTheme": storeSettings.value.sideTheme,
"theme": storeSettings.value.theme
};
localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
}
localStorage.setItem("layout-setting", JSON.stringify(layoutSetting))
setTimeout(proxy.$modal.closeLoading(), 1000)
}
function resetSetting() {
proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...");
proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...")
localStorage.removeItem("layout-setting")
setTimeout("window.location.reload()", 1000)
}
function openSetting() {
showSettings.value = true;
showSettings.value = true
}
defineExpose({
openSetting,
openSetting
})
</script>
<style lang='scss' scoped>
.setting-drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85);
color: var(--el-text-color-primary, rgba(0, 0, 0, 0.85));
line-height: 22px;
font-weight: bold;
.drawer-title {
font-size: 14px;
}
}
.setting-drawer-block-checbox {
display: flex;
justify-content: flex-start;
@@ -170,13 +193,6 @@ defineExpose({
height: 48px;
}
.custom-img {
width: 48px;
height: 38px;
border-radius: 5px;
box-shadow: 1px 1px 2px #898484;
}
.setting-drawer-block-checbox-selectIcon {
position: absolute;
top: 0;
@@ -193,7 +209,7 @@ defineExpose({
}
.drawer-item {
color: rgba(0, 0, 0, 0.65);
color: var(--el-text-color-regular, rgba(0, 0, 0, 0.65));
padding: 12px 0;
font-size: 14px;

View File

@@ -1,22 +1,22 @@
<template>
<div class="sidebar-logo-container" :class="{ 'collapse': collapse }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
<h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
<h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
</template>
<script setup>
import variables from '@/assets/styles/variables.module.scss'
import logo from '@/assets/logo/logo.png'
import useSettingsStore from '@/store/modules/settings'
import variables from '@/assets/styles/variables.module.scss'
defineProps({
collapse: {
@@ -25,9 +25,25 @@ defineProps({
}
})
const title = import.meta.env.VITE_APP_TITLE;
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
const title = import.meta.env.VITE_APP_TITLE
const settingsStore = useSettingsStore()
const sideTheme = computed(() => settingsStore.sideTheme)
// 获取Logo背景色
const getLogoBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取Logo文字颜色
const getLogoTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
return sideTheme.value === 'theme-dark' ? '#fff' : variables.menuLightText
})
</script>
<style lang="scss" scoped>
@@ -45,7 +61,7 @@ const sideTheme = computed(() => settingsStore.sideTheme);
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
background: v-bind(getLogoBackground);
text-align: center;
overflow: hidden;
@@ -63,7 +79,7 @@ const sideTheme = computed(() => settingsStore.sideTheme);
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
color: v-bind(getLogoTextColor);
font-weight: 600;
line-height: 50px;
font-size: 14px;

View File

@@ -48,20 +48,18 @@ const props = defineProps({
}
})
const onlyOneChild = ref({});
const onlyOneChild = ref({})
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
children = []
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
onlyOneChild.value = item
return true
})
// When there is only one child router, the child router is displayed by default
@@ -76,7 +74,7 @@ function hasOneShowingChild(children = [], parent) {
}
return false
};
}
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
@@ -86,7 +84,7 @@ function resolvePath(routePath, routeQuery) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
let query = JSON.parse(routeQuery)
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
@@ -94,9 +92,9 @@ function resolvePath(routePath, routeQuery) {
function hasTitle(title){
if (title.length > 5) {
return title;
return title
} else {
return "";
return ""
}
}
</script>

View File

@@ -1,16 +1,17 @@
<template>
<div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<div :class="{ 'has-logo': showLogo }" class="sidebar-container">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
:text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:background-color="getMenuBackground"
:text-color="getMenuTextColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
:class="sideTheme"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
@@ -31,24 +32,73 @@ import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const route = useRoute();
const route = useRoute()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const showLogo = computed(() => settingsStore.sidebarLogo);
const sideTheme = computed(() => settingsStore.sideTheme);
const theme = computed(() => settingsStore.theme);
const isCollapse = computed(() => !appStore.sidebar.opened);
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
const showLogo = computed(() => settingsStore.sidebarLogo)
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
const activeMenu = computed(() => {
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu;
// 获取菜单背景色
const getMenuBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
return path;
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取菜单文字颜色
const getMenuTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
})
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
</script>
<style lang="scss" scoped>
.sidebar-container {
background-color: v-bind(getMenuBackground);
.scrollbar-wrapper {
background-color: v-bind(getMenuBackground);
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
.el-menu-item, .el-sub-menu__title {
&:hover {
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
}
}
.el-menu-item {
color: v-bind(getMenuTextColor);
&.is-active {
color: var(--menu-active-text, #409eff);
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
}
}
.el-sub-menu__title {
color: v-bind(getMenuTextColor);
}
}
}
</style>

View File

@@ -12,35 +12,37 @@
<script setup>
import useTagsViewStore from '@/store/modules/tagsView'
const tagAndTagSpacing = ref(4);
const { proxy } = getCurrentInstance();
const tagAndTagSpacing = ref(4)
const { proxy } = getCurrentInstance()
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef);
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef)
onMounted(() => {
scrollWrapper.value.addEventListener('scroll', emitScroll, true)
})
onBeforeUnmount(() => {
scrollWrapper.value.removeEventListener('scroll', emitScroll)
})
function handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = scrollWrapper.value;
const $scrollWrapper = scrollWrapper.value
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
}
const emits = defineEmits()
const emitScroll = () => {
emits('scroll')
}
const tagsViewStore = useTagsViewStore()
const visitedViews = computed(() => tagsViewStore.visitedViews);
const visitedViews = computed(() => tagsViewStore.visitedViews)
function moveToTarget(currentTag) {
const $container = proxy.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = scrollWrapper.value;
const $scrollWrapper = scrollWrapper.value
let firstTag = null
let lastTag = null
@@ -56,17 +58,17 @@ function moveToTarget(currentTag) {
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
} else {
const tagListDom = document.getElementsByClassName('tags-view-item');
const tagListDom = document.getElementsByClassName('tags-view-item')
const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
let prevTag = null
let nextTag = null
for (const k in tagListDom) {
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
prevTag = tagListDom[k];
prevTag = tagListDom[k]
}
if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
nextTag = tagListDom[k];
nextTag = tagListDom[k]
}
}
}

View File

@@ -5,13 +5,14 @@
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="isActive(tag) ? 'active' : ''"
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" />
{{ tag.title }}
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
@@ -48,25 +49,27 @@ import useTagsViewStore from '@/store/modules/tagsView'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const visible = ref(false);
const top = ref(0);
const left = ref(0);
const selectedTag = ref({});
const affixTags = ref([]);
const scrollPaneRef = ref(null);
const visible = ref(false)
const top = ref(0)
const left = ref(0)
const selectedTag = ref({})
const affixTags = ref([])
const scrollPaneRef = ref(null)
const { proxy } = getCurrentInstance();
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance()
const route = useRoute()
const router = useRouter()
const visitedViews = computed(() => useTagsViewStore().visitedViews);
const routes = computed(() => usePermissionStore().routes);
const theme = computed(() => useSettingsStore().theme);
const visitedViews = computed(() => useTagsViewStore().visitedViews)
const routes = computed(() => usePermissionStore().routes)
const theme = computed(() => useSettingsStore().theme)
const tagsIcon = computed(() => useSettingsStore().tagsIcon)
watch(route, () => {
addTags()
moveToCurrentTag()
})
watch(visible, (value) => {
if (value) {
document.body.addEventListener('click', closeMenu)
@@ -74,6 +77,7 @@ watch(visible, (value) => {
document.body.removeEventListener('click', closeMenu)
}
})
onMounted(() => {
initTags()
addTags()
@@ -82,16 +86,19 @@ onMounted(() => {
function isActive(r) {
return r.path === route.path
}
function activeStyle(tag) {
if (!isActive(tag)) return {};
if (!isActive(tag)) return {}
return {
"background-color": theme.value,
"border-color": theme.value
};
}
}
function isAffix(tag) {
return tag.meta && tag.meta.affix
}
function isFirstView() {
try {
return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
@@ -99,6 +106,7 @@ function isFirstView() {
return false
}
}
function isLastView() {
try {
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
@@ -106,6 +114,7 @@ function isLastView() {
return false
}
}
function filterAffixTags(routes, basePath = '') {
let tags = []
routes.forEach(route => {
@@ -127,9 +136,10 @@ function filterAffixTags(routes, basePath = '') {
})
return tags
}
function initTags() {
const res = filterAffixTags(routes.value);
affixTags.value = res;
const res = filterAffixTags(routes.value)
affixTags.value = res
for (const tag of res) {
// Must have tag name
if (tag.name) {
@@ -137,21 +147,19 @@ function initTags() {
}
}
}
function addTags() {
const { name } = route
if (name) {
useTagsViewStore().addView(route)
if (route.meta.link) {
useTagsViewStore().addIframeView(route);
}
}
return false
}
function moveToCurrentTag() {
nextTick(() => {
for (const r of visitedViews.value) {
if (r.path === route.path) {
scrollPaneRef.value.moveToTarget(r);
scrollPaneRef.value.moveToTarget(r)
// when query is different then update
if (r.fullPath !== route.fullPath) {
useTagsViewStore().updateVisitedView(route)
@@ -160,12 +168,14 @@ function moveToCurrentTag() {
}
})
}
function refreshSelectedTag(view) {
proxy.$tab.refreshPage(view);
proxy.$tab.refreshPage(view)
if (route.meta.link) {
useTagsViewStore().delIframeView(route);
useTagsViewStore().delIframeView(route)
}
}
function closeSelectedTag(view) {
proxy.$tab.closePage(view).then(({ visitedViews }) => {
if (isActive(view)) {
@@ -173,6 +183,7 @@ function closeSelectedTag(view) {
}
})
}
function closeRightTags() {
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
@@ -180,6 +191,7 @@ function closeRightTags() {
}
})
}
function closeLeftTags() {
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
@@ -187,12 +199,14 @@ function closeLeftTags() {
}
})
}
function closeOthersTags() {
router.push(selectedTag.value).catch(() => { });
router.push(selectedTag.value).catch(() => { })
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
moveToCurrentTag()
})
}
function closeAllTags(view) {
proxy.$tab.closeAllPage().then(({ visitedViews }) => {
if (affixTags.value.some(tag => tag.path === route.path)) {
@@ -201,6 +215,7 @@ function closeAllTags(view) {
toLastView(visitedViews, view)
})
}
function toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
@@ -216,6 +231,7 @@ function toLastView(visitedViews, view) {
}
}
}
function openMenu(tag, e) {
const menuMinWidth = 105
const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
@@ -233,21 +249,24 @@ function openMenu(tag, e) {
visible.value = true
selectedTag.value = tag
}
function closeMenu() {
visible.value = false
}
function handleScroll() {
closeMenu()
}
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
background: var(--tags-bg, #fff);
border-bottom: 1px solid var(--tags-item-border, #d8dce5);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
@@ -255,25 +274,29 @@ function handleScroll() {
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
border: 1px solid var(--tags-item-border, #d8dce5);
color: var(--tags-item-text, #495060);
background: var(--tags-item-bg, #fff);
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: "";
content: '';
background: #fff;
display: inline-block;
width: 8px;
@@ -285,9 +308,14 @@ function handleScroll() {
}
}
}
.tags-view-item.active.has-icon::before {
content: none !important;
}
.contextmenu {
margin: 0;
background: #fff;
background: var(--el-bg-color-overlay, #fff);
z-index: 3000;
position: absolute;
list-style-type: none;
@@ -295,14 +323,17 @@ function handleScroll() {
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
color: var(--tags-item-text, #333);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
border: 1px solid var(--el-border-color-light, #e4e7ed);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
background: var(--tags-item-hover, #eee);
}
}
}
@@ -319,15 +350,17 @@ function handleScroll() {
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(0.6);
transform: scale(.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
background-color: var(--tags-close-hover, #b4bccc);
color: #fff;
width: 12px !important;
height: 12px !important;

View File

@@ -17,18 +17,16 @@
import { useWindowSize } from '@vueuse/core'
import Sidebar from './components/Sidebar/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components'
import defaultSettings from '@/settings'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme);
const sideTheme = computed(() => settingsStore.sideTheme);
const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
const needTagsView = computed(() => settingsStore.tagsView);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const theme = computed(() => settingsStore.theme)
const sideTheme = computed(() => settingsStore.sideTheme)
const sidebar = computed(() => useAppStore().sidebar)
const device = computed(() => useAppStore().device)
const needTagsView = computed(() => settingsStore.tagsView)
const fixedHeader = computed(() => settingsStore.fixedHeader)
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,
@@ -37,13 +35,16 @@ const classObj = computed(() => ({
mobile: device.value === 'mobile'
}))
const { width, height } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
const { width, height } = useWindowSize()
const WIDTH = 992 // refer to Bootstrap's responsive design
watchEffect(() => {
watch(() => device.value, () => {
if (device.value === 'mobile' && sidebar.value.opened) {
useAppStore().closeSideBar({ withoutAnimation: false })
}
})
watchEffect(() => {
if (width.value - 1 < WIDTH) {
useAppStore().toggleDevice('mobile')
useAppStore().closeSideBar({ withoutAnimation: true })
@@ -56,18 +57,18 @@ function handleClickOutside() {
useAppStore().closeSideBar({ withoutAnimation: false })
}
const settingRef = ref(null);
const settingRef = ref(null)
function setLayout() {
settingRef.value.openSetting();
settingRef.value.openSetting()
}
</script>
<style lang="scss" scoped>
@import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss";
@use "@/assets/styles/mixin.scss" as mix;
@use "@/assets/styles/variables.module.scss" as vars;
.app-wrapper {
@include clearfix;
@include mix.clearfix;
position: relative;
height: 100%;
width: 100%;
@@ -78,6 +79,11 @@ function setLayout() {
}
}
.main-container:has(.fixed-header) {
height: 100vh;
overflow: hidden;
}
.drawer-bg {
background: #000;
opacity: 0.3;
@@ -93,7 +99,7 @@ function setLayout() {
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
width: calc(100% - #{vars.$base-sidebar-width});
transition: width 0.28s;
}

View File

@@ -4,6 +4,7 @@ import Cookies from 'js-cookie'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import locale from 'element-plus/es/locale/lang/zh-cn'
import '@/assets/styles/index.scss' // global css
@@ -25,6 +26,7 @@ import elementIcons from '@/components/SvgIcon/svgicon'
import './permission' // permission control
import { useDict } from '@/utils/dict'
import { getConfigKey } from "@/api/system/config"
import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
// 分页组件
@@ -39,8 +41,6 @@ import FileUpload from "@/components/FileUpload"
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 自定义树选择组件
import TreeSelect from '@/components/TreeSelect'
// 字典标签组件
import DictTag from '@/components/DictTag'
@@ -53,13 +53,13 @@ app.config.globalProperties.parseTime = parseTime
app.config.globalProperties.resetForm = resetForm
app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.getConfigKey = getConfigKey
app.config.globalProperties.selectDictLabel = selectDictLabel
app.config.globalProperties.selectDictLabels = selectDictLabels
// 全局组件挂载
app.component('DictTag', DictTag)
app.component('Pagination', Pagination)
app.component('TreeSelect', TreeSelect)
app.component('FileUpload', FileUpload)
app.component('ImageUpload', ImageUpload)
app.component('ImagePreview', ImagePreview)

View File

@@ -3,15 +3,19 @@ import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
import { isHttp, isPathMatch } from '@/utils/validate'
import { isRelogin } from '@/utils/request'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
NProgress.configure({ showSpinner: false });
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register'];
const whiteList = ['/login', '/register']
const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
}
router.beforeEach((to, from, next) => {
NProgress.start()
@@ -21,7 +25,7 @@ router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else if (whiteList.indexOf(to.path) !== -1) {
} else if (isWhiteList(to.path)) {
next()
} else {
if (useUserStore().roles.length === 0) {
@@ -50,7 +54,7 @@ router.beforeEach((to, from, next) => {
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
if (isWhiteList(to.path)) {
// 在免登录白名单,直接进入
next()
} else {

View File

@@ -1,7 +1,7 @@
import useUserStore from '@/store/modules/user'
function authPermission(permission) {
const all_permission = "*:*:*";
const all_permission = "*:*:*"
const permissions = useUserStore().permissions
if (permission && permission.length > 0) {
return permissions.some(v => {
@@ -13,7 +13,7 @@ function authPermission(permission) {
}
function authRole(role) {
const super_admin = "admin";
const super_admin = "admin"
const roles = useUserStore().roles
if (role && role.length > 0) {
return roles.some(v => {
@@ -27,7 +27,7 @@ function authRole(role) {
export default {
// 验证用户是否具备某权限
hasPermi(permission) {
return authPermission(permission);
return authPermission(permission)
},
// 验证用户是否含有指定权限,只需包含其中一个
hasPermiOr(permissions) {
@@ -43,7 +43,7 @@ export default {
},
// 验证用户是否具备某角色
hasRole(role) {
return authRole(role);
return authRole(role)
},
// 验证用户是否含有指定角色,只需包含其中一个
hasRoleOr(roles) {

View File

@@ -26,9 +26,10 @@ const sessionCache = {
if (value != null) {
return JSON.parse(value)
}
return null
},
remove (key) {
sessionStorage.removeItem(key);
sessionStorage.removeItem(key)
}
}
const localCache = {
@@ -59,9 +60,10 @@ const localCache = {
if (value != null) {
return JSON.parse(value)
}
return null
},
remove (key) {
localStorage.removeItem(key);
localStorage.removeItem(key)
}
}

View File

@@ -6,7 +6,7 @@ import errorCode from '@/utils/errorCode'
import { blobValidate } from '@/utils/ruoyi'
const baseURL = import.meta.env.VITE_APP_BASE_API
let downloadLoadingInstance;
let downloadLoadingInstance
export default {
name(name, isDelete = true) {
@@ -17,29 +17,29 @@ export default {
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
const isBlob = blobValidate(res.data)
if (isBlob) {
const blob = new Blob([res.data])
this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
} else {
this.printErrMsg(res.data);
this.printErrMsg(res.data)
}
})
},
resource(resource) {
var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource);
var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource)
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
const isBlob = blobValidate(res.data)
if (isBlob) {
const blob = new Blob([res.data])
this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
} else {
this.printErrMsg(res.data);
this.printErrMsg(res.data)
}
})
},
@@ -52,28 +52,28 @@ export default {
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data);
const isBlob = blobValidate(res.data)
if (isBlob) {
const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
this.printErrMsg(res.data)
}
downloadLoadingInstance.close();
downloadLoadingInstance.close()
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
downloadLoadingInstance.close()
})
},
saveAs(text, name, opts) {
saveAs(text, name, opts);
saveAs(text, name, opts)
},
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const resText = await data.text()
const rspObj = JSON.parse(resText)
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
ElMessage.error(errMsg)
}
}

View File

@@ -1,6 +1,6 @@
import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
let loadingInstance;
let loadingInstance
export default {
// 消息提示
@@ -41,7 +41,7 @@ export default {
},
// 错误通知
notifyError(content) {
ElNotification.error(content);
ElNotification.error(content)
},
// 成功通知
notifySuccess(content) {
@@ -77,6 +77,6 @@ export default {
},
// 关闭遮罩层
closeLoading() {
loadingInstance.close();
loadingInstance.close()
}
}

View File

@@ -4,15 +4,15 @@ import router from '@/router'
export default {
// 刷新当前tab页签
refreshPage(obj) {
const { path, query, matched } = router.currentRoute.value;
const { path, query, matched } = router.currentRoute.value
if (obj === undefined) {
matched.forEach((m) => {
if (m.components && m.components.default && m.components.default.name) {
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
obj = { name: m.components.default.name, path: path, query: query };
obj = { name: m.components.default.name, path: path, query: query }
}
}
});
})
}
return useTagsViewStore().delCachedView(obj).then(() => {
const { path, query } = obj
@@ -24,9 +24,9 @@ export default {
},
// 关闭当前tab页签打开新页签
closeOpenPage(obj) {
useTagsViewStore().delView(router.currentRoute.value);
useTagsViewStore().delView(router.currentRoute.value)
if (obj !== undefined) {
return router.push(obj);
return router.push(obj)
}
},
// 关闭指定tab页签
@@ -37,33 +37,35 @@ export default {
if (latestView) {
return router.push(latestView.fullPath)
}
return router.push('/');
});
return router.push('/')
})
}
return useTagsViewStore().delView(obj);
return useTagsViewStore().delView(obj)
},
// 关闭所有tab页签
closeAllPage() {
return useTagsViewStore().delAllViews();
return useTagsViewStore().delAllViews()
},
// 关闭左侧tab页签
closeLeftPage(obj) {
return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
return useTagsViewStore().delLeftTags(obj || router.currentRoute.value)
},
// 关闭右侧tab页签
closeRightPage(obj) {
return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
return useTagsViewStore().delRightTags(obj || router.currentRoute.value)
},
// 关闭其他tab页签
closeOtherPage(obj) {
return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
return useTagsViewStore().delOthersViews(obj || router.currentRoute.value)
},
// 打开tab页签
openPage(url) {
return router.push(url);
openPage(title, url, params) {
const obj = { path: url, meta: { title: title } }
useTagsViewStore().addView(obj)
return router.push({ path: url, query: params })
},
// 修改tab页签
updatePage(obj) {
return useTagsViewStore().updateVisitedView(obj);
return useTagsViewStore().updateVisitedView(obj)
}
}

View File

@@ -77,7 +77,7 @@ export const constantRoutes = [
redirect: 'noredirect',
children: [
{
path: 'profile',
path: 'profile/:activeTab?',
component: () => import('@/views/system/user/profile/index'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
@@ -166,10 +166,9 @@ const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
return { top: 0 }
},
});
})
export default router;
export default router

View File

@@ -3,10 +3,12 @@ export default {
* 网页标题
*/
title: import.meta.env.VITE_APP_TITLE,
/**
* 侧边栏主题 深色主题theme-dark浅色主题theme-light
*/
sideTheme: 'theme-dark',
/**
* 是否系统布局配置
*/
@@ -21,6 +23,11 @@ export default {
* 是否显示 tagsView
*/
tagsView: true,
/**
* 显示页签图标
*/
tagsIcon: false,
/**
* 是否固定头部
@@ -38,10 +45,13 @@ export default {
dynamicTitle: false,
/**
* @type {string | array} 'production' | ['production', 'development']
* @description Need show err logs component.
* The default is only used in the production env
* If you want to also use it in dev, you can pass ['production', 'development']
* 是否显示底部版权
*/
errorLog: 'production'
footerVisible: false,
/**
* 底部版权文本内容
*/
footerContent: 'Copyright © 2018-2025 RuoYi. All Rights Reserved.'
}

View File

@@ -15,7 +15,7 @@ const useAppStore = defineStore(
actions: {
toggleSideBar(withoutAnimation) {
if (this.sidebar.hide) {
return false;
return false
}
this.sidebar.opened = !this.sidebar.opened
this.sidebar.withoutAnimation = withoutAnimation
@@ -34,7 +34,7 @@ const useAppStore = defineStore(
this.device = device
},
setSize(size) {
this.size = size;
this.size = size
Cookies.set('size', size)
},
toggleSideBarHide(status) {

View File

@@ -8,16 +8,16 @@ const useDictStore = defineStore(
// 获取字典
getDict(_key) {
if (_key == null && _key == "") {
return null;
return null
}
try {
for (let i = 0; i < this.dict.length; i++) {
if (this.dict[i].key == _key) {
return this.dict[i].value;
return this.dict[i].value
}
}
} catch (e) {
return null;
return null
}
},
// 设置字典
@@ -26,27 +26,27 @@ const useDictStore = defineStore(
this.dict.push({
key: _key,
value: value
});
})
}
},
// 删除字典
removeDict(_key) {
var bln = false;
var bln = false
try {
for (let i = 0; i < this.dict.length; i++) {
if (this.dict[i].key == _key) {
this.dict.splice(i, 1);
return true;
this.dict.splice(i, 1)
return true
}
}
} catch (e) {
bln = false;
bln = false
}
return bln;
return bln
},
// 清空字典
cleanDict() {
this.dict = new Array();
this.dict = new Array()
},
// 初始字典
initDict() {

View File

@@ -85,28 +85,13 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView' && !lastRouter) {
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
childrenMap.forEach(el => {
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
if (el.children && el.children.length && el.component === 'ParentView') {
children = children.concat(filterChildren(el.children, el))
} else {
children.push(el)
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path
if (el.children && el.children.length) {
children = children.concat(filterChildren(el.children, el))
return
}
}
children = children.concat(el)
})
return children
}
@@ -129,14 +114,14 @@ export function filterDynamicRoutes(routes) {
}
export const loadView = (view) => {
let res;
let res
for (const path in modules) {
const dir = path.split('views/')[1].split('.vue')[0];
const dir = path.split('views/')[1].split('.vue')[0]
if (dir === view) {
res = () => modules[path]();
res = () => modules[path]()
}
}
return res;
return res
}
export default usePermissionStore

View File

@@ -1,7 +1,11 @@
import defaultSettings from '@/settings'
import { useDark, useToggle } from '@vueuse/core'
import { useDynamicTitle } from '@/utils/dynamicTitle'
const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
const isDark = useDark()
const toggleDark = useToggle(isDark)
const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
@@ -15,9 +19,13 @@ const useSettingsStore = defineStore(
showSettings: showSettings,
topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle
dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible,
footerContent: footerContent,
isDark: isDark.value
}),
actions: {
// 修改布局设置
@@ -30,7 +38,12 @@ const useSettingsStore = defineStore(
// 设置网页标题
setTitle(title) {
this.title = title
useDynamicTitle();
useDynamicTitle()
},
// 切换暗黑模式
toggleTheme() {
this.isDark = !this.isDark
toggleDark()
}
}
})

View File

@@ -1,5 +1,8 @@
import router from '@/router'
import { ElMessageBox, } from 'element-plus'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
const useUserStore = defineStore(
@@ -9,6 +12,7 @@ const useUserStore = defineStore(
token: getToken(),
id: '',
name: '',
nickName: '',
avatar: '',
roles: [],
permissions: []
@@ -35,8 +39,10 @@ const useUserStore = defineStore(
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
let avatar = user.avatar || ""
if (!isHttp(avatar)) {
avatar = (isEmpty(avatar)) ? defAva : import.meta.env.VITE_APP_BASE_API + avatar
}
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
this.roles = res.roles
this.permissions = res.permissions
@@ -45,7 +51,20 @@ const useUserStore = defineStore(
}
this.id = user.userId
this.name = user.userName
this.nickName = user.nickName
this.avatar = avatar
/* 初始密码提示 */
if(res.isDefaultModifyPwd) {
ElMessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
}).catch(() => {})
}
/* 过期密码提示 */
if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
ElMessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
}).catch(() => {})
}
resolve(res)
}).catch(error => {
reject(error)

View File

@@ -5,20 +5,20 @@ import { getDicts } from '@/api/system/dict/data'
* 获取字典数据
*/
export function useDict(...args) {
const res = ref({});
const res = ref({})
return (() => {
args.forEach((dictType, index) => {
res.value[dictType] = [];
const dicts = useDictStore().getDict(dictType);
res.value[dictType] = []
const dicts = useDictStore().getDict(dictType)
if (dicts) {
res.value[dictType] = dicts;
res.value[dictType] = dicts
} else {
getDicts(dictType).then(resp => {
res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
useDictStore().setDict(dictType, res.value[dictType]);
useDictStore().setDict(dictType, res.value[dictType])
})
}
})
return toRefs(res.value);
return toRefs(res.value)
})()
}

View File

@@ -1,4 +1,3 @@
import store from '@/store'
import defaultSettings from '@/settings'
import useSettingsStore from '@/store/modules/settings'
@@ -6,10 +5,10 @@ import useSettingsStore from '@/store/modules/settings'
* 动态修改标题
*/
export function useDynamicTitle() {
const settingsStore = useSettingsStore();
const settingsStore = useSettingsStore()
if (settingsStore.dynamicTitle) {
document.title = settingsStore.title + ' - ' + defaultSettings.title;
document.title = settingsStore.title + ' - ' + defaultSettings.title
} else {
document.title = defaultSettings.title;
document.title = defaultSettings.title
}
}

View File

@@ -0,0 +1,452 @@
export const formConf = {
formRef: 'formRef',
formModel: 'formData',
size: 'default',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true,
}
export const inputComponents = [
{
label: '单行文本',
tag: 'el-input',
tagIcon: 'input',
type: 'text',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/input',
},
{
label: '多行文本',
tag: 'el-input',
tagIcon: 'textarea',
type: 'textarea',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
labelWidth: null,
autosize: {
minRows: 4,
maxRows: 4,
},
style: { width: '100%' },
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/input',
},
{
label: '密码',
tag: 'el-input',
tagIcon: 'password',
type: 'password',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
'show-password': true,
labelWidth: null,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/input',
},
{
label: '计数器',
tag: 'el-input-number',
tagIcon: 'number',
placeholder: '',
defaultValue: undefined,
span: 24,
labelWidth: null,
min: undefined,
max: undefined,
step: undefined,
'step-strictly': false,
precision: undefined,
'controls-position': '',
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/input-number',
},
]
export const selectComponents = [
{
label: '下拉选择',
tag: 'el-select',
tagIcon: 'select',
placeholder: '请选择',
defaultValue: undefined,
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
disabled: false,
required: true,
filterable: false,
multiple: false,
options: [
{
label: '选项一',
value: 1,
},
{
label: '选项二',
value: 2,
},
],
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/select',
},
{
label: '级联选择',
tag: 'el-cascader',
tagIcon: 'cascader',
placeholder: '请选择',
defaultValue: [],
span: 24,
labelWidth: null,
style: { width: '100%' },
props: {
props: {
multiple: false,
},
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
required: true,
options: [
{
id: 1,
value: 1,
label: '选项1',
children: [
{
id: 2,
value: 2,
label: '选项1-1',
},
],
},
],
dataType: 'dynamic',
labelKey: 'label',
valueKey: 'value',
childrenKey: 'children',
separator: '/',
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/cascader',
},
{
label: '单选框组',
tag: 'el-radio-group',
tagIcon: 'radio',
defaultValue: 0,
span: 24,
labelWidth: null,
style: {},
optionType: 'default',
border: false,
size: 'default',
disabled: false,
required: true,
options: [
{
label: '选项一',
value: 1,
},
{
label: '选项二',
value: 2,
},
],
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/radio',
},
{
label: '多选框组',
tag: 'el-checkbox-group',
tagIcon: 'checkbox',
defaultValue: [],
span: 24,
labelWidth: null,
style: {},
optionType: 'default',
border: false,
size: 'default',
disabled: false,
required: true,
options: [
{
label: '选项一',
value: 1,
},
{
label: '选项二',
value: 2,
},
],
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/checkbox',
},
{
label: '开关',
tag: 'el-switch',
tagIcon: 'switch',
defaultValue: false,
span: 24,
labelWidth: null,
style: {},
disabled: false,
required: true,
'active-text': '',
'inactive-text': '',
'active-color': null,
'inactive-color': null,
'active-value': true,
'inactive-value': false,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/switch',
},
{
label: '滑块',
tag: 'el-slider',
tagIcon: 'slider',
defaultValue: null,
span: 24,
labelWidth: null,
disabled: false,
required: true,
min: 0,
max: 100,
step: 1,
'show-stops': false,
range: false,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/slider',
},
{
label: '时间选择',
tag: 'el-time-picker',
tagIcon: 'time',
placeholder: '请选择',
defaultValue: '',
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/time-picker',
},
{
label: '时间范围',
tag: 'el-time-picker',
tagIcon: 'time-range',
defaultValue: null,
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
'is-range': true,
'range-separator': '至',
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/time-picker',
},
{
label: '日期选择',
tag: 'el-date-picker',
tagIcon: 'date',
placeholder: '请选择',
defaultValue: null,
type: 'date',
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
format: 'YYYY-MM-DD',
'value-format': 'YYYY-MM-DD',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/date-picker',
},
{
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
labelWidth: null,
style: { width: '100%' },
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
required: true,
format: 'YYYY-MM-DD',
'value-format': 'YYYY-MM-DD',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/date-picker',
},
{
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
labelWidth: null,
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/rate',
},
{
label: '颜色选择',
tag: 'el-color-picker',
tagIcon: 'color',
defaultValue: null,
labelWidth: null,
'show-alpha': false,
'color-format': '',
disabled: false,
required: true,
size: 'default',
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/color-picker',
},
{
label: '上传',
tag: 'el-upload',
tagIcon: 'upload',
action: 'https://jsonplaceholder.typicode.com/posts/',
defaultValue: null,
labelWidth: null,
disabled: false,
required: true,
accept: '',
name: 'file',
'auto-upload': true,
showTip: false,
buttonText: '点击上传',
fileSize: 2,
sizeUnit: 'MB',
'list-type': 'text',
multiple: false,
regList: [],
changeTag: true,
document: 'https://element-plus.org/zh-CN/component/upload',
tip: '只能上传不超过 2MB 的文件',
style: { width: '100%' },
},
]
export const layoutComponents = [
{
layout: 'rowFormItem',
tagIcon: 'row',
type: 'default',
justify: 'start',
align: 'top',
label: '行容器',
layoutTree: true,
children: [],
document: 'https://element-plus.org/zh-CN/component/layout',
},
{
layout: 'colFormItem',
label: '按钮',
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
default: '主要按钮',
type: 'primary',
icon: 'Search',
size: 'default',
disabled: false,
document: 'https://element-plus.org/zh-CN/component/button',
},
]
// 组件rule的触发方式无触发方式的组件不生成rule
export const trigger = {
'el-input': 'blur',
'el-input-number': 'blur',
'el-select': 'change',
'el-radio-group': 'change',
'el-checkbox-group': 'change',
'el-cascader': 'change',
'el-time-picker': 'change',
'el-date-picker': 'change',
'el-rate': 'change',
}

View File

@@ -0,0 +1,18 @@
const styles = {
'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
'el-upload': '.el-upload__tip{line-height: 1.2;}'
}
function addCss(cssList, el) {
const css = styles[el.tag]
css && cssList.indexOf(css) === -1 && cssList.push(css)
if (el.children) {
el.children.forEach(el2 => addCss(cssList, el2))
}
}
export function makeUpCss(conf) {
const cssList = []
conf.fields.forEach(el => addCss(cssList, el))
return cssList.join('\n')
}

View File

@@ -0,0 +1,37 @@
export const drawingDefaultValue = []
export function initDrawingDefaultValue() {
if (drawingDefaultValue.length === 0) {
drawingDefaultValue.push({
layout: 'colFormItem',
tagIcon: 'input',
label: '手机号',
vModel: 'mobile',
formId: 6,
tag: 'el-input',
placeholder: '请输入手机号',
defaultValue: '',
span: 24,
style: {width: '100%'},
clearable: true,
prepend: '',
append: '',
'prefix-icon': 'Cellphone',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false,
required: true,
changeTag: true,
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
})
}
}
export function cleanDrawingDefaultValue() {
drawingDefaultValue.splice(0, drawingDefaultValue.length)
}

359
src/utils/generator/html.js Normal file
View File

@@ -0,0 +1,359 @@
/* eslint-disable max-len */
import { trigger } from './config'
let confGlobal
let someSpanIsNot24
export function dialogWrapper(str) {
return `<el-dialog v-model="dialogVisible" @open="onOpen" @close="onClose" title="Dialog Titile">
${str}
<template #footer>
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handelConfirm">确定</el-button>
</template>
</el-dialog>`
}
export function vueTemplate(str) {
return `<template>
<div class="app-container">
${str}
</div>
</template>`
}
export function vueScript(str) {
return `<script setup>
${str}
</script>`
}
export function cssStyle(cssStr) {
return `<style>
${cssStr}
</style>`
}
function buildFormTemplate(conf, child, type) {
let labelPosition = ''
if (conf.labelPosition !== 'right') {
labelPosition = `label-position="${conf.labelPosition}"`
}
const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : ''
let str = `<el-form ref="${conf.formRef}" :model="${conf.formModel}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${conf.labelWidth}px" ${labelPosition}>
${child}
${buildFromBtns(conf, type)}
</el-form>`
if (someSpanIsNot24) {
str = `<el-row :gutter="${conf.gutter}">
${str}
</el-row>`
}
return str
}
function buildFromBtns(conf, type) {
let str = ''
if (conf.formBtns && type === 'file') {
str = `<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>`
if (someSpanIsNot24) {
str = `<el-col :span="24">
${str}
</el-col>`
}
}
return str
}
// span不为24的用el-col包裹
function colWrapper(element, str) {
if (someSpanIsNot24 || element.span !== 24) {
return `<el-col :span="${element.span}">
${str}
</el-col>`
}
return str
}
const layouts = {
colFormItem(element) {
let labelWidth = ''
if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${element.labelWidth}px"`
}
const required = !trigger[element.tag] && element.required ? 'required' : ''
const tagDom = tags[element.tag] ? tags[element.tag](element) : null
let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}>
${tagDom}
</el-form-item>`
str = colWrapper(element, str)
return str
},
rowFormItem(element) {
const type = element.type === 'default' ? '' : `type="${element.type}"`
const justify = element.type === 'default' ? '' : `justify="${element.justify}"`
const align = element.type === 'default' ? '' : `align="${element.align}"`
const gutter = element.gutter ? `gutter="${element.gutter}"` : ''
const children = element.children.map(el => layouts[el.layout](el))
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
${children.join('\n')}
</el-row>`
str = colWrapper(element, str)
return str
}
}
const tags = {
'el-button': el => {
const {
tag, disabled
} = attrBuilder(el)
const type = el.type ? `type="${el.type}"` : ''
const icon = el.icon ? `icon="${el.icon}"` : ''
const size = el.size ? `size="${el.size}"` : ''
let child = buildElButtonChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-input': el => {
const {
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : ''
const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''
const readonly = el.readonly ? 'readonly' : ''
const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : ''
const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : ''
const showPassword = el['show-password'] ? 'show-password' : ''
const type = el.type ? `type="${el.type}"` : ''
const autosize = el.autosize && el.autosize.minRows
? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"`
: ''
let child = buildElInputChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>`
},
'el-input-number': el => {
const { disabled, vModel, placeholder } = attrBuilder(el)
const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
const step = el.step ? `:step='${el.step}'` : ''
const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''
const precision = el.precision ? `:precision='${el.precision}'` : ''
return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>`
},
'el-select': el => {
const {
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const filterable = el.filterable ? 'filterable' : ''
const multiple = el.multiple ? 'multiple' : ''
let child = buildElSelectChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>`
},
'el-radio-group': el => {
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
let child = buildElRadioGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-checkbox-group': el => {
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const min = el.min ? `:min="${el.min}"` : ''
const max = el.max ? `:max="${el.max}"` : ''
let child = buildElCheckboxGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-switch': el => {
const { disabled, vModel } = attrBuilder(el)
const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : ''
const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : ''
const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : ''
const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : ''
const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''
const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''
return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>`
},
'el-cascader': el => {
const {
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const options = el.options ? `:options="${el.vModel}Options"` : ''
const props = el.props ? `:props="${el.vModel}Props"` : ''
const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"'
const filterable = el.filterable ? 'filterable' : ''
const separator = el.separator === '/' ? '' : `separator="${el.separator}"`
return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>`
},
'el-slider': el => {
const { disabled, vModel } = attrBuilder(el)
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
const step = el.step ? `:step='${el.step}'` : ''
const range = el.range ? 'range' : ''
const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : ''
return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>`
},
'el-time-picker': el => {
const {
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
const isRange = el['is-range'] ? 'is-range' : ''
const format = el.format ? `format="${el.format}"` : ''
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''
return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>`
},
'el-date-picker': el => {
const {
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : ''
const format = el.format ? `format="${el.format}"` : ''
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
const type = el.type === 'date' ? '' : `type="${el.type}"`
const readonly = el.readonly ? 'readonly' : ''
return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>`
},
'el-rate': el => {
const { disabled, vModel } = attrBuilder(el)
const max = el.max ? `:max='${el.max}'` : ''
const allowHalf = el['allow-half'] ? 'allow-half' : ''
const showText = el['show-text'] ? 'show-text' : ''
const showScore = el['show-score'] ? 'show-score' : ''
return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>`
},
'el-color-picker': el => {
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const showAlpha = el['show-alpha'] ? 'show-alpha' : ''
const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : ''
return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>`
},
'el-upload': el => {
const disabled = el.disabled ? ':disabled=\'true\'' : ''
const action = el.action ? `:action="${el.vModel}Action"` : ''
const multiple = el.multiple ? 'multiple' : ''
const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : ''
const accept = el.accept ? `accept="${el.accept}"` : ''
const name = el.name !== 'file' ? `name="${el.name}"` : ''
const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : ''
const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"`
const fileList = `:file-list="${el.vModel}fileList"`
const ref = `ref="${el.vModel}"`
let child = buildElUploadChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>`
}
}
function attrBuilder(el) {
return {
vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`,
clearable: el.clearable ? 'clearable' : '',
placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
disabled: el.disabled ? ':disabled=\'true\'' : ''
}
}
// el-buttin 子级
function buildElButtonChild(conf) {
const children = []
if (conf.default) {
children.push(conf.default)
}
return children.join('\n')
}
// el-input innerHTML
function buildElInputChild(conf) {
const children = []
if (conf.prepend) {
children.push(`<template slot="prepend">${conf.prepend}</template>`)
}
if (conf.append) {
children.push(`<template slot="append">${conf.append}</template>`)
}
return children.join('\n')
}
function buildElSelectChild(conf) {
const children = []
if (conf.options && conf.options.length) {
children.push(`<el-option v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
}
return children.join('\n')
}
function buildElRadioGroupChild(conf) {
const children = []
if (conf.options && conf.options.length) {
const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio'
const border = conf.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :value="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
function buildElCheckboxGroupChild(conf) {
const children = []
if (conf.options && conf.options.length) {
const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
const border = conf.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :value="item.label" :disabled="item.disabled" ${border} />`)
}
return children.join('\n')
}
function buildElUploadChild(conf) {
const list = []
if (conf['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>')
else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${conf.buttonText}</el-button>`)
if (conf.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${conf.fileSize}${conf.sizeUnit}${conf.accept}文件</div>`)
return list.join('\n')
}
export function makeUpHtml(conf, type) {
const htmlList = []
confGlobal = conf
someSpanIsNot24 = conf.fields.some(item => item.span !== 24)
conf.fields.forEach(el => {
htmlList.push(layouts[el.layout](el))
})
const htmlStr = htmlList.join('\n')
let temp = buildFormTemplate(conf, htmlStr, type)
if (type === 'dialog') {
temp = dialogWrapper(temp)
}
confGlobal = null
return temp
}

View File

@@ -0,0 +1 @@
["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"]

370
src/utils/generator/js.js Normal file
View File

@@ -0,0 +1,370 @@
import { titleCase } from '@/utils/index'
import { trigger } from './config'
// 文件大小设置
const units = {
KB: '1024',
MB: '1024 / 1024',
GB: '1024 / 1024 / 1024',
}
/**
* @name: 生成js需要的数据
* @description: 生成js需要的数据
* @param {*} conf
* @param {*} type 弹窗或表单
* @return {*}
*/
export function makeUpJs(conf, type) {
conf = JSON.parse(JSON.stringify(conf))
const dataList = []
const ruleList = []
const optionsList = []
const propsList = []
const methodList = []
const uploadVarList = []
conf.fields.forEach((el) => {
buildAttributes(
el,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
)
})
const script = buildexport(
conf,
type,
dataList.join('\n'),
ruleList.join('\n'),
optionsList.join('\n'),
uploadVarList.join('\n'),
propsList.join('\n'),
methodList.join('\n')
)
return script
}
/**
* @name: 生成参数
* @description: 生成参数,包括表单数据表单验证数据,多选选项数据,上传数据等
* @return {*}
*/
function buildAttributes(
el,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
){
buildData(el, dataList)
buildRules(el, ruleList)
if (el.options && el.options.length) {
buildOptions(el, optionsList)
if (el.dataType === 'dynamic') {
const model = `${el.vModel}Options`
const options = titleCase(model)
buildOptionMethod(`get${options}`, model, methodList)
}
}
if (el.props && el.props.props) {
buildProps(el, propsList)
}
if (el.action && el.tag === 'el-upload') {
uploadVarList.push(
`
// 上传请求路径
const ${el.vModel}Action = ref('${el.action}')
// 上传文件列表
const ${el.vModel}fileList = ref([])`
)
methodList.push(buildBeforeUpload(el))
if (!el['auto-upload']) {
methodList.push(buildSubmitUpload(el))
}
}
if (el.children) {
el.children.forEach((el2) => {
buildAttributes(
el2,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
)
})
}
}
/**
* @name: 生成表单数据formData
* @description: 生成表单数据formData
* @param {*} conf
* @param {*} dataList 数据列表
* @return {*}
*/
function buildData(conf, dataList) {
if (conf.vModel === undefined) return
let defaultValue
if (typeof conf.defaultValue === 'string' && !conf.multiple) {
defaultValue = `'${conf.defaultValue}'`
} else {
defaultValue = `${JSON.stringify(conf.defaultValue)}`
}
dataList.push(`${conf.vModel}: ${defaultValue},`)
}
/**
* @name: 生成表单验证数据rule
* @description: 生成表单验证数据rule
* @param {*} conf
* @param {*} ruleList 验证数据列表
* @return {*}
*/
function buildRules(conf, ruleList) {
if (conf.vModel === undefined) return
const rules = []
if (trigger[conf.tag]) {
if (conf.required) {
const type = Array.isArray(conf.defaultValue) ? "type: 'array'," : ''
let message = Array.isArray(conf.defaultValue)
? `请至少选择一个${conf.vModel}`
: conf.placeholder
if (message === undefined) message = `${conf.label}不能为空`
rules.push(
`{ required: true, ${type} message: '${message}', trigger: '${
trigger[conf.tag]
}' }`
)
}
if (conf.regList && Array.isArray(conf.regList)) {
conf.regList.forEach((item) => {
if (item.pattern) {
rules.push(
`{ pattern: new RegExp(${item.pattern}), message: '${
item.message
}', trigger: '${trigger[conf.tag]}' }`
)
}
})
}
ruleList.push(`${conf.vModel}: [${rules.join(',')}],`)
}
}
/**
* @name: 生成选项数据
* @description: 生成选项数据,单选多选下拉等
* @param {*} conf
* @param {*} optionsList 选项数据列表
* @return {*}
*/
function buildOptions(conf, optionsList) {
if (conf.vModel === undefined) return
if (conf.dataType === 'dynamic') {
conf.options = []
}
const str = `const ${conf.vModel}Options = ref(${JSON.stringify(conf.options)})`
optionsList.push(str)
}
/**
* @name: 生成方法
* @description: 生成方法
* @param {*} methodName 方法名
* @param {*} model
* @param {*} methodList 方法列表
* @return {*}
*/
function buildOptionMethod(methodName, model, methodList) {
const str = `function ${methodName}() {
// TODO 发起请求获取数据
${model}.value
}`
methodList.push(str)
}
/**
* @name: 生成表单组件需要的props设置
* @description: 生成表单组件需要的props设置级联组件
* @param {*} conf
* @param {*} propsList
* @return {*}
*/
function buildProps(conf, propsList) {
if (conf.dataType === 'dynamic') {
conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey)
conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey)
conf.childrenKey !== 'children' &&
(conf.props.props.children = conf.childrenKey)
}
const str = `
// props设置
const ${conf.vModel}Props = ref(${JSON.stringify(conf.props.props)})`
propsList.push(str)
}
/**
* @name: 生成上传组件的相关内容
* @description: 生成上传组件的相关内容
* @param {*} conf
* @return {*}
*/
function buildBeforeUpload(conf) {
const unitNum = units[conf.sizeUnit]
let rightSizeCode = ''
let acceptCode = ''
const returnList = []
if (conf.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize}
if(!isRightSize){
proxy.$modal.msgError('文件大小超过 ${conf.fileSize}${conf.sizeUnit}')
}`
returnList.push('isRightSize')
}
if (conf.accept) {
acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type)
if(!isAccept){
proxy.$modal.msgError('应该选择${conf.accept}类型的文件')
}`
returnList.push('isAccept')
}
const str = `
/**
* @name: 上传之前的文件判断
* @description: 上传之前的文件判断,判断文件大小文件类型等
* @param {*} file
* @return {*}
*/
function ${conf.vModel}BeforeUpload(file) {
${rightSizeCode}
${acceptCode}
return ${returnList.join('&&')}
}`
return returnList.length ? str : ''
}
/**
* @name: 生成提交表单方法
* @description: 生成提交表单方法
* @param {Object} conf vModel 表单ref
* @return {*}
*/
function buildSubmitUpload(conf) {
const str = `function submitUpload() {
this.$refs['${conf.vModel}'].submit()
}`
return str
}
/**
* @name: 组装js代码
* @description: 组装js代码方法
* @return {*}
*/
function buildexport(
conf,
type,
data,
rules,
selectOptions,
uploadVar,
props,
methods
) {
let str = `
const { proxy } = getCurrentInstance()
const ${conf.formRef} = ref()
const data = reactive({
${conf.formModel}: {
${data}
},
${conf.formRules}: {
${rules}
}
})
const {${conf.formModel}, ${conf.formRules}} = toRefs(data)
${selectOptions}
${uploadVar}
${props}
${methods}
`
if(type === 'dialog') {
str += `
// 弹窗设置
const dialogVisible = defineModel()
// 弹窗确认回调
const emit = defineEmits(['confirm'])
/**
* @name: 弹窗打开后执行
* @description: 弹窗打开后执行方法
* @return {*}
*/
function onOpen(){
}
/**
* @name: 弹窗关闭时执行
* @description: 弹窗关闭方法,重置表单
* @return {*}
*/
function onClose(){
${conf.formRef}.value.resetFields()
}
/**
* @name: 弹窗取消
* @description: 弹窗取消方法
* @return {*}
*/
function close(){
dialogVisible.value = false
}
/**
* @name: 弹窗表单提交
* @description: 弹窗表单提交方法
* @return {*}
*/
function handelConfirm(){
${conf.formRef}.value.validate((valid) => {
if (!valid) return
// TODO 提交表单
close()
// 回调父级组件
emit('confirm')
})
}
`
} else {
str += `
/**
* @name: 表单提交
* @description: 表单提交方法
* @return {*}
*/
function submitForm() {
${conf.formRef}.value.validate((valid) => {
if (!valid) return
// TODO 提交表单
})
}
/**
* @name: 表单重置
* @description: 表单重置方法
* @return {*}
*/
function resetForm() {
${conf.formRef}.value.resetFields()
}
`
}
return str
}

View File

@@ -0,0 +1,156 @@
import { defineComponent, h } from 'vue'
import { makeMap } from '@/utils/index'
const isAttr = makeMap(
'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +
'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +
'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +
'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +
'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +
'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' +
'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +
'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +
'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +
'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +
'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +
'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +
'target,title,type,usemap,value,width,wrap' + 'prefix-icon'
)
const isNotProps = makeMap(
'layout,prepend,regList,tag,document,changeTag,defaultValue'
)
function useVModel(props, emit) {
return {
modelValue: props.defaultValue,
'onUpdate:modelValue': (val) => emit('update:modelValue', val),
}
}
const componentChild = {
'el-button': {
default(h, conf, key) {
return conf[key]
},
},
'el-select': {
options(h, conf, key) {
return conf.options.map(item => h(resolveComponent('el-option'), {
label: item.label,
value: item.value,
}))
}
},
'el-radio-group': {
options(h, conf, key) {
return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), {
label: item.value,
}, () => item.label)) : conf.options.map(item => h(resolveComponent('el-radio'), {
label: item.value,
border: conf.border,
}, () => item.label))
}
},
'el-checkbox-group': {
options(h, conf, key) {
return conf.optionType === 'button' ? conf.options.map(item => h(resolveComponent('el-checkbox-button'), {
label: item.value,
}, () => item.label)) : conf.options.map(item => h(resolveComponent('el-checkbox'), {
label: item.value,
border: conf.border,
}, () => item.label))
}
},
'el-upload': {
'list-type': (h, conf, key) => {
const option = {}
// if (conf.showTip) {
// tip = h('div', {
// class: "el-upload__tip"
// }, () => '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件')
// }
if (conf['list-type'] === 'picture-card') {
return h(resolveComponent('el-icon'), option, () => h(resolveComponent('Plus')))
} else {
// option.size = "small"
option.type = "primary"
option.icon = "Upload"
return h(resolveComponent('el-button'), option, () => conf.buttonText)
}
},
}
}
const componentSlot = {
'el-upload': {
'tip': (h, conf, key) => {
if (conf.showTip) {
return () => h('div', {
class: "el-upload__tip"
}, '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件')
}
},
}
}
export default defineComponent({
// 使用 render 函数
render() {
const dataObject = {
attrs: {},
props: {},
on: {},
style: {}
}
const confClone = JSON.parse(JSON.stringify(this.conf))
const children = []
const slot = {}
const childObjs = componentChild[confClone.tag]
if (childObjs) {
Object.keys(childObjs).forEach(key => {
const childFunc = childObjs[key]
if (confClone[key]) {
children.push(childFunc(h, confClone, key))
}
})
}
const slotObjs = componentSlot[confClone.tag]
if (slotObjs) {
Object.keys(slotObjs).forEach(key => {
const childFunc = slotObjs[key]
if (confClone[key]) {
slot[key] = childFunc(h, confClone, key)
}
})
}
Object.keys(confClone).forEach(key => {
const val = confClone[key]
if (dataObject[key]) {
dataObject[key] = val
} else if (isAttr(key)) {
dataObject.attrs[key] = val
} else if (!isNotProps(key)) {
dataObject.props[key] = val
}
})
if(children.length > 0){
slot.default = () => children
}
return h(resolveComponent(this.conf.tag),
{
modelValue: this.$attrs.modelValue,
...dataObject.props,
...dataObject.attrs,
style: {
...dataObject.style
},
}
, slot ?? null)
},
props: {
conf: {
type: Object,
required: true,
},
}
})

View File

@@ -4,7 +4,7 @@ import { parseTime } from './ruoyi'
* 表格时间格式化
*/
export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return "";
if (cellValue == null || cellValue == "") return ""
var date = new Date(cellValue)
var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1

View File

@@ -9,7 +9,7 @@ export function checkPermi(value) {
if (value && value instanceof Array && value.length > 0) {
const permissions = useUserStore().permissions
const permissionDatas = value
const all_permission = "*:*:*";
const all_permission = "*:*:*"
const hasPermission = permissions.some(permission => {
return all_permission === permission || permissionDatas.includes(permission)
@@ -34,7 +34,7 @@ export function checkRole(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = useUserStore().roles
const permissionRoles = value
const super_admin = "admin";
const super_admin = "admin"
const hasRole = roles.some(role => {
return super_admin === role || permissionRoles.includes(role)

View File

@@ -7,9 +7,9 @@ import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import useUserStore from '@/store/modules/user'
let downloadLoadingInstance;
let downloadLoadingInstance
// 是否显示重新登录
export let isRelogin = { show: false };
export let isRelogin = { show: false }
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
@@ -31,10 +31,10 @@ service.interceptors.request.use(config => {
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
let url = config.url + '?' + tansParams(config.params)
url = url.slice(0, -1)
config.params = {}
config.url = url
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
@@ -42,22 +42,22 @@ service.interceptors.request.use(config => {
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
return config
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
const s_url = sessionObj.url // 请求地址
const s_data = sessionObj.data // 请求数据
const s_time = sessionObj.time // 请求时间
const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
const message = '数据正在处理,请勿重复提交'
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
@@ -74,7 +74,7 @@ service.interceptors.request.use(config => {
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
const code = res.data.code || 200
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
@@ -83,15 +83,15 @@ service.interceptors.response.use(res => {
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
isRelogin.show = false
useUserStore().logOut().then(() => {
location.href = '/index';
location.href = '/index'
})
}).catch(() => {
isRelogin.show = false;
});
isRelogin.show = false
})
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
@@ -109,13 +109,13 @@ service.interceptors.response.use(res => {
},
error => {
console.log('err' + error)
let { message } = error;
let { message } = error
if (message == "Network Error") {
message = "后端接口连接异常";
message = "后端接口连接异常"
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
message = "系统接口请求超时"
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
message = "系统接口" + message.substr(message.length - 3) + "异常"
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
@@ -131,21 +131,21 @@ export function download(url, params, filename, config) {
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
const isBlob = blobValidate(data)
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const resText = await data.text()
const rspObj = JSON.parse(resText)
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
ElMessage.error(errMsg)
}
downloadLoadingInstance.close();
downloadLoadingInstance.close()
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
downloadLoadingInstance.close()
})
}

View File

@@ -1,5 +1,3 @@
/**
* 通用js方法封装处理
* Copyright (c) 2019 ruoyi
@@ -18,7 +16,7 @@ export function parseTime(time, pattern) {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
@@ -49,89 +47,89 @@ export function parseTime(time, pattern) {
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
this.$refs[refName].resetFields()
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params;
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
dateRange = Array.isArray(dateRange) ? dateRange : [];
let search = params
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
dateRange = Array.isArray(dateRange) ? dateRange : []
if (typeof (propName) === 'undefined') {
search.params['beginTime'] = dateRange[0];
search.params['endTime'] = dateRange[1];
search.params['beginTime'] = dateRange[0]
search.params['endTime'] = dateRange[1]
} else {
search.params['begin' + propName] = dateRange[0];
search.params['end' + propName] = dateRange[1];
search.params['begin' + propName] = dateRange[0]
search.params['end' + propName] = dateRange[1]
}
return search;
return search
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
return ""
}
var actions = [];
var actions = []
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
actions.push(datas[key].label);
return true;
actions.push(datas[key].label)
return true
}
})
if (actions.length === 0) {
actions.push(value);
actions.push(value)
}
return actions.join('');
return actions.join('')
}
// 回显数据字典(字符串数组)
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) {
return "";
return ""
}
if (Array.isArray(value)) {
value = value.join(",");
value = value.join(",")
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
var actions = []
var currentSeparator = undefined === separator ? "," : separator
var temp = value.split(currentSeparator)
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
var match = false
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator);
match = true;
actions.push(datas[key].label + currentSeparator)
match = true
}
})
if (!match) {
actions.push(temp[val] + currentSeparator);
actions.push(temp[val] + currentSeparator)
}
})
return actions.join('').substring(0, actions.join('').length - 1);
return actions.join('').substring(0, actions.join('').length - 1)
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments, flag = true, i = 1;
var args = arguments, flag = true, i = 1
str = str.replace(/%s/g, function () {
var arg = args[i++];
var arg = args[i++]
if (typeof arg === 'undefined') {
flag = false;
return '';
flag = false
return ''
}
return arg;
});
return flag ? str : '';
return arg
})
return flag ? str : ''
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return "";
return ""
}
return str;
return str
}
// 数据合并
@@ -139,16 +137,16 @@ export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p]);
source[p] = mergeRecursive(source[p], target[p])
} else {
source[p] = target[p];
source[p] = target[p]
}
} catch (e) {
source[p] = target[p];
source[p] = target[p]
}
}
return source;
};
return source
}
/**
* 构造树型结构数据
@@ -162,43 +160,28 @@ export function handleTree(data, id, parentId, children) {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
}
var childrenListMap = {}
var tree = []
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
let id = d[config.id]
childrenListMap[id] = d
if (!d[config.childrenList]) {
d[config.childrenList] = []
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
let parentId = d[config.parentId]
let parentObj = childrenListMap[parentId]
if (!parentObj) {
tree.push(d)
} else {
parentObj[config.childrenList].push(d)
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
return tree
}
/**
@@ -208,36 +191,35 @@ export function handleTree(data, id, parentId, children) {
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
const value = params[propName]
var part = encodeURIComponent(propName) + "="
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
let params = propName + '[' + key + ']'
var subPart = encodeURIComponent(params) + "="
result += subPart + encodeURIComponent(value[key]) + "&"
}
}
} else {
result += part + encodeURIComponent(value) + "&";
result += part + encodeURIComponent(value) + "&"
}
}
}
return result
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') {
return p
};
}
let res = p.replace('//', '/')
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1)
}
return res;
return res
}
// 验证是否为blob格式

View File

@@ -1,9 +1,33 @@
/**
* 判断url是否是http或https
* 路径匹配器
* @param {string} pattern
* @param {string} path
* @returns {Boolean}
*/
export function isHttp(url) {
export function isPathMatch(pattern, path) {
const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*')
const regex = new RegExp(`^${regexPattern}$`)
return regex.test(path)
}
/**
* 判断value字符串是否为空
* @param {string} value
* @returns {Boolean}
*/
export function isEmpty(value) {
if (value == null || value == "" || value == undefined || value == "undefined") {
return true
}
return false
}
/**
* 判断url是否是http或https
* @param {string} url
* @returns {Boolean}
*/
export function isHttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}
@@ -12,7 +36,7 @@
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
@@ -75,10 +99,7 @@ export function validEmail(email) {
* @returns {Boolean}
*/
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true
}
return false
return typeof str === 'string' || str instanceof String
}
/**

View File

@@ -26,17 +26,17 @@
</template>
<script setup>
import errImage from "@/assets/401_images/401.gif";
import errImage from "@/assets/401_images/401.gif"
let { proxy } = getCurrentInstance();
let { proxy } = getCurrentInstance()
const errGif = ref(errImage + "?" + +new Date());
const errGif = ref(errImage + "?" + +new Date())
function back() {
if (proxy.$route.query.noGoBack) {
proxy.$router.push({ path: "/" });
proxy.$router.push({ path: "/" })
} else {
proxy.$router.go(-1);
proxy.$router.go(-1)
}
}
</script>

View File

@@ -1,42 +1,5 @@
<template>
<div class="app-container home">
<el-row :gutter="20">
<el-col :sm="24" :lg="24">
<blockquote class="text-warning" style="font-size: 14px">
领取阿里云通用云产品1888优惠券
<br />
<el-link
href="https://www.aliyun.com/minisite/goods?userCode=brki8iof"
type="primary"
target="_blank"
>https://www.aliyun.com/minisite/goods?userCode=brki8iof</el-link
>
<br />
领取腾讯云通用云产品2860优惠券
<br />
<el-link
href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console"
type="primary"
target="_blank"
>https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link
>
<br />
阿里云服务器折扣区
<el-link href="http://aly.ruoyi.vip" type="primary" target="_blank"
>>点我进入</el-link
>
&nbsp;&nbsp;&nbsp; 腾讯云服务器秒杀区
<el-link href="http://txy.ruoyi.vip" type="primary" target="_blank"
>>点我进入</el-link
><br />
<h4 class="text-danger">
云产品通用红包可叠加官网常规优惠使用(仅限新用户)
</h4>
</blockquote>
<hr />
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>若依后台管理框架</h2>
@@ -122,7 +85,10 @@
<s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s>
<s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s>
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=1HmEGh7zKA_CKI2E-pGInPTlC5jS9mc_&authKey=XaiUf1wfbSTEecm4lDMtIMsc6g%2BoETxjBm1BbZPr6IfuMGRj7oG4GEeu7jtzNaw%2F&noverify=0&group_code=174951577" target="_blank">174951577</a>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 174951577 </s>
<s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s>
<s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <s> 满191164766 </s>
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=EeCBXu51I1zPWRia2uskpjDRx6VrbnFN&authKey=Xm8yDxk0%2FyYGI11oxhXaQnTn4K7UwCk7Kn2MZTh3P1JxLctollAkyeySjnaILDkb&noverify=0&group_code=174569686" target="_blank">174569686</a>
</p>
<p>
<i class="el-icon-chat-dot-round"></i> 微信<a
@@ -148,6 +114,111 @@
</div>
</template>
<el-collapse accordion>
<el-collapse-item title="v3.9.0 - 2025-05-28">
<ol>
<li>优化菜单搜索查询页</li>
<li>导航栏显示昵称&设置</li>
<li>菜单管理新增路由名称</li>
<li>添加底部版权信息&开关</li>
<li>分配角色禁用不允许勾选</li>
<li>Excel导入导出支持多图片</li>
<li>添加页签图标显示开关功能</li>
<li>上传组件新增拖动排序属性</li>
<li>显隐列组件支持全选/全不选</li>
<li>初始密码支持自定义修改策略</li>
<li>账号密码支持自定义更新周期</li>
<li>代码生成列表支持按时间排序</li>
<li>支持富文本复制粘贴图片上传至url</li>
<li>支持文件&图片组件自定义地址&参数</li>
<li>升级tomcat到最新版本9.0.105</li>
<li>升级oshi到最新版本6.8.1</li>
<li>升级fastjson到最新版2.0.57</li>
<li>升级commons.io到最新版本2.19.0</li>
<li>package.json移除runjs依赖</li>
<li>package.json移除eslint依赖</li>
<li>package.json移除vue-meta依赖</li>
<li>修复代码生成主子表校验必填失效问题</li>
<li>优化前端树结构性能问题</li>
<li>优化前端处理路由函数代码</li>
<li>优化文件上传组件新增类型</li>
<li>优化顶部菜单搜索栏为多层级显示</li>
<li>优化文件&图片上传组件新增disabled属性</li>
<li>优化空指针异常时无法获取错误信息问题</li>
<li>优化定时任务字符包含多个括号导致数据错误</li>
<li>优化登录&注册页表头使用VUE_APP_TITLE配置值</li>
<li>优化导出Excel日期格式双击离开后与设定的格式不一致问题</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.8.9 - 2024-12-30">
<ol>
<li>用户管理支持分栏拖动</li>
<li>修改主题样式本地读取</li>
<li>用户头像http(s)链接支持</li>
<li>用户管理过滤掉已禁用部门</li>
<li>支持自定义显示Excel属性列</li>
<li>操作日志记录DELETE请求参数</li>
<li>白名单支持对通配符路径匹配</li>
<li>校检文件名是否包含特殊字符</li>
<li>代码生成创建表屏蔽违规的字符</li>
<li>菜单面包屑导航支持多层级显示</li>
<li>Excel注解支持wrapText是否允许内容换行</li>
<li>代码生成新增配置是否允许文件覆盖到本地</li>
<li>修复角色禁用权限不失效问题</li>
<li>修复代码生成上级菜单显示问题</li>
<li>修复导出子列表对象只能在最后的问题</li>
<li>修复TopNav无法正确获取active的问题</li>
<li>修复默认关闭Tags-Views内链页面打不开</li>
<li>升级oshi到最新版本6.6.5</li>
<li>升级tomcat到最新版本9.0.96</li>
<li>升级fastjson到最新版2.0.53</li>
<li>升级logback到最新版本1.2.13</li>
<li>升级spring-framework到最新版本5.3.39</li>
<li>升级quill到最新版本2.0.2</li>
<li>升级axios到最新版本0.28.1</li>
<li>优化身份证脱敏正则</li>
<li>优化权限更新后同步缓存</li>
<li>优化查询时间范围日期格式</li>
<li>优化参数键值更换为多行文本</li>
<li>优化导入带标题文件关闭清理</li>
<li>优化上传图片带域名不增加前缀</li>
<li>优化特殊字符密码修改失败问题</li>
<li>优化无用户编号不校验数据权限</li>
<li>优化TopNav内链菜单点击没有高亮</li>
<li>优化菜单管理切换Mini布局错乱问题</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.8.8 - 2024-06-30">
<ol>
<li>菜单管理新增路由名称</li>
<li>新增数据脱敏过滤注解</li>
<li>用户密码新增非法字符验证</li>
<li>限制用户操作数据权限范围</li>
<li>代码生成新增创建表结构功能</li>
<li>定时任务白名单配置范围缩小</li>
<li>优化代码生成主子表关联查询方式</li>
<li>Excel注解新增属性comboReadDict</li>
<li>Excel注解ColumnType类型新增文本</li>
<li>新增国际化资源文件配置</li>
<li>升级oshi到最新版本6.6.1</li>
<li>升级druid到最新版本1.2.23</li>
<li>升级core-js到最新版本3.37.1</li>
<li>更新HttpUtils中的User-Agent</li>
<li>更新compressionPlugin到6.1.2以兼容node18+</li>
<li>升级spring-security到安全版本防止漏洞风险</li>
<li>升级spring-framework到安全版本防止漏洞风险</li>
<li>优化自定义XSS注解匹配方式</li>
<li>优化缓存监控键名列表排序显示</li>
<li>优化定时任务日志默认按时间排序</li>
<li>优化默认文件大小超过2G无效的问题</li>
<li>优化查表特殊字符使用反斜杠进行转义</li>
<li>优化定时任务cron表达式小时配置显示错误问题</li>
<li>优化多个自定数据权限使用in查询,避免多次拼接</li>
<li>优化导入Excel时设置dictType属性重复查缓存问题</li>
<li>其他细节优化</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v3.8.7 - 2023-12-08">
<ol>
<li>操作日志记录部门名称</li>
@@ -988,7 +1059,7 @@
</template>
<script setup name="Index">
const version = ref('3.8.7')
const version = ref('3.9.0')
function goTarget(url) {
window.open(url, '__blank')

View File

@@ -1,7 +1,7 @@
<template>
<div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">若依后台管理系统</h3>
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
@@ -59,21 +59,24 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
<span>{{ footerContent }}</span>
</div>
</div>
</template>
<script setup>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from '@/store/modules/user'
import defaultSettings from '@/settings'
const title = import.meta.env.VITE_APP_TITLE
const footerContent = defaultSettings.footerContent
const userStore = useUserStore()
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance()
const loginForm = ref({
username: "admin",
@@ -81,85 +84,85 @@ const loginForm = ref({
rememberMe: false,
code: "",
uuid: ""
});
})
const loginRules = {
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
};
}
const codeUrl = ref("");
const loading = ref(false);
const codeUrl = ref("")
const loading = ref(false)
// 验证码开关
const captchaEnabled = ref(true);
const captchaEnabled = ref(true)
// 注册开关
const register = ref(false);
const redirect = ref(undefined);
const register = ref(false)
const redirect = ref(undefined)
watch(route, (newRoute) => {
redirect.value = newRoute.query && newRoute.query.redirect;
}, { immediate: true });
redirect.value = newRoute.query && newRoute.query.redirect
}, { immediate: true })
function handleLogin() {
proxy.$refs.loginRef.validate(valid => {
if (valid) {
loading.value = true;
loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 });
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
Cookies.set("username", loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
} else {
// 否则移除
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
Cookies.remove("username")
Cookies.remove("password")
Cookies.remove("rememberMe")
}
// 调用action的登录方法
userStore.login(loginForm.value).then(() => {
const query = route.query;
const query = route.query
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
acc[cur] = query[cur]
}
return acc;
}, {});
router.push({ path: redirect.value || "/", query: otherQueryParams });
return acc
}, {})
router.push({ path: redirect.value || "/", query: otherQueryParams })
}).catch(() => {
loading.value = false;
loading.value = false
// 重新获取验证码
if (captchaEnabled.value) {
getCode();
getCode()
}
});
})
}
});
})
}
function getCode() {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img;
loginForm.value.uuid = res.uuid;
codeUrl.value = "data:image/gif;base64," + res.img
loginForm.value.uuid = res.uuid
}
});
})
}
function getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
const username = Cookies.get("username")
const password = Cookies.get("password")
const rememberMe = Cookies.get("rememberMe")
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
};
}
}
getCode();
getCookie();
getCode()
getCookie()
</script>
<style lang='scss' scoped>
@@ -182,6 +185,7 @@ getCookie();
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
z-index: 1;
.el-input {
height: 40px;
input {

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-row>
<el-row :gutter="10">
<el-col :span="24" class="card-box">
<el-card>
<template #header><Monitor style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">基本信息</span></template>
@@ -65,21 +65,21 @@
</template>
<script setup name="Cache">
import { getCache } from '@/api/monitor/cache';
import * as echarts from 'echarts';
import { getCache } from '@/api/monitor/cache'
import * as echarts from 'echarts'
const cache = ref([]);
const commandstats = ref(null);
const usedmemory = ref(null);
const { proxy } = getCurrentInstance();
const cache = ref([])
const commandstats = ref(null)
const usedmemory = ref(null)
const { proxy } = getCurrentInstance()
function getList() {
proxy.$modal.loading("正在加载缓存监控数据,请稍候!");
proxy.$modal.loading("正在加载缓存监控数据,请稍候!")
getCache().then(response => {
proxy.$modal.closeLoading();
cache.value = response.data;
proxy.$modal.closeLoading()
cache.value = response.data
const commandstatsIntance = echarts.init(commandstats.value, "macarons");
const commandstatsIntance = echarts.init(commandstats.value, "macarons")
commandstatsIntance.setOption({
tooltip: {
trigger: "item",
@@ -97,8 +97,8 @@ function getList() {
animationDuration: 1000
}
]
});
const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
})
const usedmemoryInstance = echarts.init(usedmemory.value, "macarons")
usedmemoryInstance.setOption({
tooltip: {
formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
@@ -122,11 +122,11 @@ function getList() {
]
})
window.addEventListener("resize", () => {
commandstatsIntance.resize();
usedmemoryInstance.resize();
});
commandstatsIntance.resize()
usedmemoryInstance.resize()
})
})
}
getList();
getList()
</script>

View File

@@ -155,92 +155,92 @@
</template>
<script setup name="CacheList">
import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache";
import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache"
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance()
const cacheNames = ref([]);
const cacheKeys = ref([]);
const cacheForm = ref({});
const loading = ref(true);
const subLoading = ref(false);
const nowCacheName = ref("");
const tableHeight = ref(window.innerHeight - 200);
const cacheNames = ref([])
const cacheKeys = ref([])
const cacheForm = ref({})
const loading = ref(true)
const subLoading = ref(false)
const nowCacheName = ref("")
const tableHeight = ref(window.innerHeight - 200)
/** 查询缓存名称列表 */
function getCacheNames() {
loading.value = true;
loading.value = true
listCacheName().then(response => {
cacheNames.value = response.data;
loading.value = false;
});
cacheNames.value = response.data
loading.value = false
})
}
/** 刷新缓存名称列表 */
function refreshCacheNames() {
getCacheNames();
proxy.$modal.msgSuccess("刷新缓存列表成功");
getCacheNames()
proxy.$modal.msgSuccess("刷新缓存列表成功")
}
/** 清理指定名称缓存 */
function handleClearCacheName(row) {
clearCacheName(row.cacheName).then(response => {
proxy.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功");
getCacheKeys();
});
proxy.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功")
getCacheKeys()
})
}
/** 查询缓存键名列表 */
function getCacheKeys(row) {
const cacheName = row !== undefined ? row.cacheName : nowCacheName.value;
const cacheName = row !== undefined ? row.cacheName : nowCacheName.value
if (cacheName === "") {
return;
return
}
subLoading.value = true;
subLoading.value = true
listCacheKey(cacheName).then(response => {
cacheKeys.value = response.data;
subLoading.value = false;
nowCacheName.value = cacheName;
});
cacheKeys.value = response.data
subLoading.value = false
nowCacheName.value = cacheName
})
}
/** 刷新缓存键名列表 */
function refreshCacheKeys() {
getCacheKeys();
proxy.$modal.msgSuccess("刷新键名列表成功");
getCacheKeys()
proxy.$modal.msgSuccess("刷新键名列表成功")
}
/** 清理指定键名缓存 */
function handleClearCacheKey(cacheKey) {
clearCacheKey(cacheKey).then(response => {
proxy.$modal.msgSuccess("清理缓存键名[" + cacheKey + "]成功");
getCacheKeys();
});
proxy.$modal.msgSuccess("清理缓存键名[" + cacheKey + "]成功")
getCacheKeys()
})
}
/** 列表前缀去除 */
function nameFormatter(row) {
return row.cacheName.replace(":", "");
return row.cacheName.replace(":", "")
}
/** 键名前缀去除 */
function keyFormatter(cacheKey) {
return cacheKey.replace(nowCacheName.value, "");
return cacheKey.replace(nowCacheName.value, "")
}
/** 查询缓存内容详细 */
function handleCacheValue(cacheKey) {
getCacheValue(nowCacheName.value, cacheKey).then(response => {
cacheForm.value = response.data;
});
cacheForm.value = response.data
})
}
/** 清理全部缓存 */
function handleClearCacheAll() {
clearCacheAll().then(response => {
proxy.$modal.msgSuccess("清理全部缓存成功");
});
proxy.$modal.msgSuccess("清理全部缓存成功")
})
}
getCacheNames();
getCacheNames()
</script>

View File

@@ -7,7 +7,7 @@
<script setup>
import iFrame from '@/components/iFrame'
import { ref } from 'vue';
import { ref } from 'vue'
const url = ref(import.meta.env.VITE_APP_BASE_API + '/druid/login.html');
const url = ref(import.meta.env.VITE_APP_BASE_API + '/druid/login.html')
</script>

View File

@@ -196,7 +196,7 @@
<el-radio
v-for="dict in sys_job_status"
:key="dict.value"
:label="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
@@ -204,17 +204,17 @@
<el-col :span="12">
<el-form-item label="执行策略" prop="misfirePolicy">
<el-radio-group v-model="form.misfirePolicy">
<el-radio-button label="1">立即执行</el-radio-button>
<el-radio-button label="2">执行一次</el-radio-button>
<el-radio-button label="3">放弃执行</el-radio-button>
<el-radio-button value="1">立即执行</el-radio-button>
<el-radio-button value="2">执行一次</el-radio-button>
<el-radio-button value="3">放弃执行</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发" prop="concurrent">
<el-radio-group v-model="form.concurrent">
<el-radio-button label="0">允许</el-radio-button>
<el-radio-button label="1">禁止</el-radio-button>
<el-radio-button value="0">允许</el-radio-button>
<el-radio-button value="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
@@ -285,24 +285,25 @@
</template>
<script setup name="Job">
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
import Crontab from '@/components/Crontab'
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status");
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job"
const jobList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const openView = ref(false);
const openCron = ref(false);
const expression = ref("");
const router = useRouter()
const { proxy } = getCurrentInstance()
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status")
const jobList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const openView = ref(false)
const openCron = ref(false)
const expression = ref("")
const data = reactive({
form: {},
@@ -318,28 +319,31 @@ const data = reactive({
invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }],
cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "change" }]
}
});
})
const { queryParams, form, rules } = toRefs(data);
const { queryParams, form, rules } = toRefs(data)
/** 查询定时任务列表 */
function getList() {
loading.value = true;
loading.value = true
listJob(queryParams.value).then(response => {
jobList.value = response.rows;
total.value = response.total;
loading.value = false;
});
jobList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 任务组名字典翻译 */
function jobGroupFormat(row, column) {
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup);
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup)
}
/** 取消按钮 */
function cancel() {
open.value = false;
reset();
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
@@ -351,133 +355,148 @@ function reset() {
misfirePolicy: 1,
concurrent: 1,
status: "0"
};
proxy.resetForm("jobRef");
}
proxy.resetForm("jobRef")
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
proxy.resetForm("queryRef")
handleQuery()
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.jobId);
single.value = selection.length != 1;
multiple.value = !selection.length;
ids.value = selection.map(item => item.jobId)
single.value = selection.length != 1
multiple.value = !selection.length
}
// 更多操作触发
function handleCommand(command, row) {
switch (command) {
case "handleRun":
handleRun(row);
break;
handleRun(row)
break
case "handleView":
handleView(row);
break;
handleView(row)
break
case "handleJobLog":
handleJobLog(row);
break;
handleJobLog(row)
break
default:
break;
break
}
}
// 任务状态修改
function handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用";
let text = row.status === "0" ? "启用" : "停用"
proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
return changeJobStatus(row.jobId, row.status);
return changeJobStatus(row.jobId, row.status)
}).then(() => {
proxy.$modal.msgSuccess(text + "成功");
proxy.$modal.msgSuccess(text + "成功")
}).catch(function () {
row.status = row.status === "0" ? "1" : "0";
});
row.status = row.status === "0" ? "1" : "0"
})
}
/* 立即执行一次 */
function handleRun(row) {
proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
return runJob(row.jobId, row.jobGroup);
return runJob(row.jobId, row.jobGroup)
}).then(() => {
proxy.$modal.msgSuccess("执行成功");})
.catch(() => {});
proxy.$modal.msgSuccess("执行成功")})
.catch(() => {})
}
/** 任务详细信息 */
function handleView(row) {
getJob(row.jobId).then(response => {
form.value = response.data;
openView.value = true;
});
form.value = response.data
openView.value = true
})
}
/** cron表达式按钮操作 */
function handleShowCron() {
expression.value = form.value.cronExpression;
openCron.value = true;
expression.value = form.value.cronExpression
openCron.value = true
}
/** 确定后回传值 */
function crontabFill(value) {
form.value.cronExpression = value;
form.value.cronExpression = value
}
/** 任务日志列表查询 */
function handleJobLog(row) {
const jobId = row.jobId || 0;
const jobId = row.jobId || 0
router.push('/monitor/job-log/index/' + jobId)
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加任务";
reset()
open.value = true
title.value = "添加任务"
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const jobId = row.jobId || ids.value;
reset()
const jobId = row.jobId || ids.value
getJob(jobId).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改任务";
});
form.value = response.data
open.value = true
title.value = "修改任务"
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["jobRef"].validate(valid => {
if (valid) {
if (form.value.jobId != undefined) {
updateJob(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addJob(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
});
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const jobIds = row.jobId || ids.value;
const jobIds = row.jobId || ids.value
proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
return delJob(jobIds);
return delJob(jobIds)
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("monitor/job/export", {
...queryParams.value,
}, `job_${new Date().getTime()}.xlsx`);
}, `job_${new Date().getTime()}.xlsx`)
}
getList();
getList()
</script>

View File

@@ -171,21 +171,21 @@
</template>
<script setup name="JobLog">
import { getJob } from "@/api/monitor/job";
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
import { getJob } from "@/api/monitor/job"
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog"
const { proxy } = getCurrentInstance();
const { sys_common_status, sys_job_group } = proxy.useDict("sys_common_status", "sys_job_group");
const { proxy } = getCurrentInstance()
const { sys_common_status, sys_job_group } = proxy.useDict("sys_common_status", "sys_job_group")
const jobLogList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref([]);
const route = useRoute();
const jobLogList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const multiple = ref(true)
const total = ref(0)
const dateRange = ref([])
const route = useRoute()
const data = reactive({
form: {},
@@ -196,82 +196,88 @@ const data = reactive({
dictType: undefined,
status: undefined
}
});
})
const { queryParams, form, rules } = toRefs(data);
const { queryParams, form, rules } = toRefs(data)
/** 查询调度日志列表 */
function getList() {
loading.value = true;
loading.value = true
listJobLog(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
jobLogList.value = response.rows;
total.value = response.total;
loading.value = false;
});
jobLogList.value = response.rows
total.value = response.total
loading.value = false
})
}
// 返回按钮
function handleClose() {
const obj = { path: "/monitor/job" };
proxy.$tab.closeOpenPage(obj);
const obj = { path: "/monitor/job" }
proxy.$tab.closeOpenPage(obj)
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
handleQuery();
dateRange.value = []
proxy.resetForm("queryRef")
handleQuery()
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.jobLogId);
multiple.value = !selection.length;
ids.value = selection.map(item => item.jobLogId)
multiple.value = !selection.length
}
/** 详细按钮操作 */
function handleView(row) {
open.value = true;
form.value = row;
open.value = true
form.value = row
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$modal.confirm('是否确认删除调度日志编号为"' + ids.value + '"的数据项?').then(function () {
return delJobLog(ids.value);
return delJobLog(ids.value)
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 清空按钮操作 */
function handleClean() {
proxy.$modal.confirm("是否确认清空所有调度日志数据项?").then(function () {
return cleanJobLog();
return cleanJobLog()
}).then(() => {
getList();
proxy.$modal.msgSuccess("清空成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("清空成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("monitor/jobLog/export", {
...queryParams.value,
}, `job_log_${new Date().getTime()}.xlsx`);
}, `job_log_${new Date().getTime()}.xlsx`)
}
(() => {
const jobId = route.params && route.params.jobId;
const jobId = route.params && route.params.jobId
if (jobId !== undefined && jobId != 0) {
getJob(jobId).then(response => {
queryParams.value.jobName = response.data.jobName;
queryParams.value.jobGroup = response.data.jobGroup;
getList();
});
queryParams.value.jobName = response.data.jobName
queryParams.value.jobGroup = response.data.jobGroup
getList()
})
} else {
getList();
getList()
}
})();
getList();
})()
</script>

View File

@@ -125,21 +125,21 @@
</template>
<script setup name="Logininfor">
import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor";
import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor"
const { proxy } = getCurrentInstance();
const { sys_common_status } = proxy.useDict("sys_common_status");
const { proxy } = getCurrentInstance()
const { sys_common_status } = proxy.useDict("sys_common_status")
const logininforList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const selectName = ref("");
const total = ref(0);
const dateRange = ref([]);
const defaultSort = ref({ prop: "loginTime", order: "descending" });
const logininforList = ref([])
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const selectName = ref("")
const total = ref(0)
const dateRange = ref([])
const defaultSort = ref({ prop: "loginTime", order: "descending" })
// 查询参数
const queryParams = ref({
@@ -150,76 +150,84 @@ const queryParams = ref({
status: undefined,
orderByColumn: undefined,
isAsc: undefined
});
})
/** 查询登录日志列表 */
function getList() {
loading.value = true;
loading.value = true
list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
logininforList.value = response.rows;
total.value = response.total;
loading.value = false;
});
logininforList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
queryParams.value.pageNum = 1;
proxy.$refs["logininforRef"].sort(defaultSort.value.prop, defaultSort.value.order);
dateRange.value = []
proxy.resetForm("queryRef")
queryParams.value.pageNum = 1
proxy.$refs["logininforRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.infoId);
multiple.value = !selection.length;
single.value = selection.length != 1;
selectName.value = selection.map(item => item.userName);
ids.value = selection.map(item => item.infoId)
multiple.value = !selection.length
single.value = selection.length != 1
selectName.value = selection.map(item => item.userName)
}
/** 排序触发事件 */
function handleSortChange(column, prop, order) {
queryParams.value.orderByColumn = column.prop;
queryParams.value.isAsc = column.order;
getList();
queryParams.value.orderByColumn = column.prop
queryParams.value.isAsc = column.order
getList()
}
/** 删除按钮操作 */
function handleDelete(row) {
const infoIds = row.infoId || ids.value;
const infoIds = row.infoId || ids.value
proxy.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?').then(function () {
return delLogininfor(infoIds);
return delLogininfor(infoIds)
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 清空按钮操作 */
function handleClean() {
proxy.$modal.confirm("是否确认清空所有登录日志数据项?").then(function () {
return cleanLogininfor();
return cleanLogininfor()
}).then(() => {
getList();
proxy.$modal.msgSuccess("清空成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("清空成功")
}).catch(() => {})
}
/** 解锁按钮操作 */
function handleUnlock() {
const username = selectName.value;
const username = selectName.value
proxy.$modal.confirm('是否确认解锁用户"' + username + '"数据项?').then(function () {
return unlockLogininfor(username);
return unlockLogininfor(username)
}).then(() => {
proxy.$modal.msgSuccess("用户" + username + "解锁成功");
}).catch(() => {});
proxy.$modal.msgSuccess("用户" + username + "解锁成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("monitor/logininfor/export", {
...queryParams.value,
}, `config_${new Date().getTime()}.xlsx`);
}, `logininfor_${new Date().getTime()}.xlsx`)
}
getList();
getList()
</script>

View File

@@ -58,49 +58,52 @@
</template>
<script setup name="Online">
import { forceLogout, list as initData } from "@/api/monitor/online";
import { forceLogout, list as initData } from "@/api/monitor/online"
const { proxy } = getCurrentInstance();
const { proxy } = getCurrentInstance()
const onlineList = ref([]);
const loading = ref(true);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
const onlineList = ref([])
const loading = ref(true)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
const queryParams = ref({
ipaddr: undefined,
userName: undefined
});
})
/** 查询登录日志列表 */
function getList() {
loading.value = true;
loading.value = true
initData(queryParams.value).then(response => {
onlineList.value = response.rows;
total.value = response.total;
loading.value = false;
});
onlineList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 搜索按钮操作 */
function handleQuery() {
pageNum.value = 1;
getList();
pageNum.value = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
proxy.resetForm("queryRef")
handleQuery()
}
/** 强退按钮操作 */
function handleForceLogout(row) {
proxy.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function () {
return forceLogout(row.tokenId);
return forceLogout(row.tokenId)
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
getList();
getList()
</script>

View File

@@ -198,22 +198,22 @@
</template>
<script setup name="Operlog">
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog";
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog"
const { proxy } = getCurrentInstance();
const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type","sys_common_status");
const { proxy } = getCurrentInstance()
const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type", "sys_common_status")
const operlogList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
const defaultSort = ref({ prop: "operTime", order: "descending" });
const operlogList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const dateRange = ref([])
const defaultSort = ref({ prop: "operTime", order: "descending" })
const data = reactive({
form: {},
@@ -226,76 +226,85 @@ const data = reactive({
businessType: undefined,
status: undefined
}
});
})
const { queryParams, form } = toRefs(data);
const { queryParams, form } = toRefs(data)
/** 查询登录日志 */
function getList() {
loading.value = true;
loading.value = true
list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
operlogList.value = response.rows;
total.value = response.total;
loading.value = false;
});
operlogList.value = response.rows
total.value = response.total
loading.value = false
})
}
/** 操作日志类型字典翻译 */
function typeFormat(row, column) {
return proxy.selectDictLabel(sys_oper_type.value, row.businessType);
return proxy.selectDictLabel(sys_oper_type.value, row.businessType)
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
queryParams.value.pageNum = 1;
proxy.$refs["operlogRef"].sort(defaultSort.value.prop, defaultSort.value.order);
dateRange.value = []
proxy.resetForm("queryRef")
queryParams.value.pageNum = 1
proxy.$refs["operlogRef"].sort(defaultSort.value.prop, defaultSort.value.order)
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.operId);
multiple.value = !selection.length;
ids.value = selection.map(item => item.operId)
multiple.value = !selection.length
}
/** 排序触发事件 */
function handleSortChange(column, prop, order) {
queryParams.value.orderByColumn = column.prop;
queryParams.value.isAsc = column.order;
getList();
queryParams.value.orderByColumn = column.prop
queryParams.value.isAsc = column.order
getList()
}
/** 详细按钮操作 */
function handleView(row) {
open.value = true;
form.value = row;
open.value = true
form.value = row
}
/** 删除按钮操作 */
function handleDelete(row) {
const operIds = row.operId || ids.value;
const operIds = row.operId || ids.value
proxy.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?').then(function () {
return delOperlog(operIds);
return delOperlog(operIds)
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 清空按钮操作 */
function handleClean() {
proxy.$modal.confirm("是否确认清空所有操作日志数据项?").then(function () {
return cleanOperlog();
return cleanOperlog()
}).then(() => {
getList();
proxy.$modal.msgSuccess("清空成功");
}).catch(() => {});
getList()
proxy.$modal.msgSuccess("清空成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("monitor/operlog/export",{
...queryParams.value,
}, `config_${new Date().getTime()}.xlsx`);
}, `config_${new Date().getTime()}.xlsx`)
}
getList();
getList()
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<el-row>
<el-row :gutter="10">
<el-col :span="12" class="card-box">
<el-card>
<template #header><Cpu style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">CPU</span></template>
@@ -172,16 +172,16 @@
<script setup>
import { getServer } from '@/api/monitor/server'
const server = ref([]);
const { proxy } = getCurrentInstance();
const server = ref([])
const { proxy } = getCurrentInstance()
function getList() {
proxy.$modal.loading("正在加载服务监控数据,请稍候!");
proxy.$modal.loading("正在加载服务监控数据,请稍候!")
getServer().then(response => {
server.value = response.data;
proxy.$modal.closeLoading();
});
server.value = response.data
proxy.$modal.closeLoading()
})
}
getList();
getList()
</script>

View File

@@ -5,8 +5,8 @@
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute();
const router = useRouter();
const route = useRoute()
const router = useRouter()
const { params, query } = route
const { path } = params

View File

@@ -1,7 +1,7 @@
<template>
<div class="register">
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<h3 class="title">若依后台管理系统</h3>
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username">
<el-input
v-model="registerForm.username"
@@ -70,17 +70,20 @@
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
<span>{{ footerContent }}</span>
</div>
</div>
</template>
<script setup>
import { ElMessageBox } from "element-plus";
import { getCodeImg, register } from "@/api/login";
import { ElMessageBox } from "element-plus"
import { getCodeImg, register } from "@/api/login"
import defaultSettings from '@/settings'
const router = useRouter();
const { proxy } = getCurrentInstance();
const title = import.meta.env.VITE_APP_TITLE
const footerContent = defaultSettings.footerContent
const router = useRouter()
const { proxy } = getCurrentInstance()
const registerForm = ref({
username: "",
@@ -88,15 +91,15 @@ const registerForm = ref({
confirmPassword: "",
code: "",
uuid: ""
});
})
const equalToPassword = (rule, value, callback) => {
if (registerForm.value.password !== value) {
callback(new Error("两次输入的密码不一致"));
callback(new Error("两次输入的密码不一致"))
} else {
callback();
callback()
}
};
}
const registerRules = {
username: [
@@ -105,52 +108,53 @@ const registerRules = {
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" },
{ min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }
{ min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" },
{ pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
confirmPassword: [
{ required: true, trigger: "blur", message: "请再次输入您的密码" },
{ required: true, validator: equalToPassword, trigger: "blur" }
],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
};
}
const codeUrl = ref("");
const loading = ref(false);
const captchaEnabled = ref(true);
const codeUrl = ref("")
const loading = ref(false)
const captchaEnabled = ref(true)
function handleRegister() {
proxy.$refs.registerRef.validate(valid => {
if (valid) {
loading.value = true;
loading.value = true
register(registerForm.value).then(res => {
const username = registerForm.value.username;
const username = registerForm.value.username
ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " 注册成功!</font>", "系统提示", {
dangerouslyUseHTMLString: true,
type: "success",
}).then(() => {
router.push("/login");
}).catch(() => {});
router.push("/login")
}).catch(() => {})
}).catch(() => {
loading.value = false;
loading.value = false
if (captchaEnabled) {
getCode();
getCode()
}
});
})
}
});
})
}
function getCode() {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img;
registerForm.value.uuid = res.uuid;
codeUrl.value = "data:image/gif;base64," + res.img
registerForm.value.uuid = res.uuid
}
});
})
}
getCode();
getCode()
</script>
<style lang='scss' scoped>

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