mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-05-21 14:48:05 +00:00
Compare commits
166 Commits
v5.3.1-v2.
...
v5.6.0-v2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bc3c618fe | ||
|
|
0076f5f6f7 | ||
|
|
ef5ea98a03 | ||
|
|
73f2374c72 | ||
|
|
9dcb392220 | ||
|
|
54636ac14f | ||
|
|
51a852caea | ||
|
|
767b00c257 | ||
|
|
c9f9fbed49 | ||
|
|
41c8e06c54 | ||
|
|
33a397032c | ||
|
|
5b55687a76 | ||
|
|
7fd45ab2e8 | ||
|
|
3e592c1c5e | ||
|
|
2db01677e3 | ||
|
|
656366d610 | ||
|
|
eb8bdd5655 | ||
|
|
1681a32dbc | ||
|
|
705e68759d | ||
|
|
6208a2d0ca | ||
|
|
9cae1bb675 | ||
|
|
6b9802dfe1 | ||
|
|
5c9c940588 | ||
|
|
419e7bde9a | ||
|
|
3c7691a6b7 | ||
|
|
32ee077f1a | ||
|
|
dfd1dc29d1 | ||
|
|
6b8600a989 | ||
|
|
3724baa93a | ||
|
|
b411505b19 | ||
|
|
1e5f89817e | ||
|
|
c28a224d78 | ||
|
|
3008a8d7b0 | ||
|
|
56bb05d547 | ||
|
|
b4282f1423 | ||
|
|
f9c3958d5d | ||
|
|
0e210b90a2 | ||
|
|
6a17a0735d | ||
|
|
8284a87d36 | ||
|
|
cdad26bba6 | ||
|
|
ab9b1a1367 | ||
|
|
8048d80baa | ||
|
|
f1ef2b1083 | ||
|
|
52ea8895d6 | ||
|
|
5e1d44c2af | ||
|
|
55691695c4 | ||
|
|
53e7d03a1c | ||
|
|
9c84bf242c | ||
|
|
b89e9cee7f | ||
|
|
014bedd301 | ||
|
|
ceb6de9044 | ||
|
|
04c6131fb0 | ||
|
|
c9cfefdc3e | ||
|
|
fbe9254114 | ||
|
|
1b46739799 | ||
|
|
88056a5067 | ||
|
|
3da18c9464 | ||
|
|
b4a40c94dc | ||
|
|
c11b91a48e | ||
|
|
65da8dfa93 | ||
|
|
e10ef50288 | ||
|
|
4c607f6915 | ||
|
|
43b4e74c9c | ||
|
|
f84e95d735 | ||
|
|
dba12f25e2 | ||
|
|
257ececa52 | ||
|
|
7d36621c44 | ||
|
|
a29d03b231 | ||
|
|
ab99104240 | ||
|
|
153758df82 | ||
|
|
5e5fca8f6b | ||
|
|
d23bf73a2e | ||
|
|
ad7058b739 | ||
|
|
0dd5044bbe | ||
|
|
ae5dd09ba2 | ||
|
|
0e20743c28 | ||
|
|
9223fabde7 | ||
|
|
1282839f67 | ||
|
|
952f56ca2e | ||
|
|
e08f41dd72 | ||
|
|
2392f64233 | ||
|
|
1565ec1996 | ||
|
|
d95f358d1b | ||
|
|
7ea5199fd2 | ||
|
|
4280c7177d | ||
|
|
4472b24def | ||
|
|
33cf333b2a | ||
|
|
27a427eb97 | ||
|
|
e2e1ce4091 | ||
|
|
4013c06fea | ||
|
|
8a029f6c4c | ||
|
|
c785a9fb7f | ||
|
|
95cbd2f3af | ||
|
|
edacb79ccb | ||
|
|
0872624adc | ||
|
|
1bf03053e1 | ||
|
|
47c2724058 | ||
|
|
219ab65eb7 | ||
|
|
107b2d444b | ||
|
|
a8bb81c984 | ||
|
|
0ca453d549 | ||
|
|
093c05bda0 | ||
|
|
94dcc28c8a | ||
|
|
35b016b3ba | ||
|
|
f6d69e2bea | ||
|
|
9573343afc | ||
|
|
8f99c76e72 | ||
|
|
b000788785 | ||
|
|
62f7d393f3 | ||
|
|
31037db627 | ||
|
|
4e0d946676 | ||
|
|
71dceeacc2 | ||
|
|
d59259737f | ||
|
|
8afe7c3931 | ||
|
|
d59738b473 | ||
|
|
2f35342782 | ||
|
|
720c822bb3 | ||
|
|
48b5d595df | ||
|
|
1034399fe4 | ||
|
|
ba257e2357 | ||
|
|
8bd26758dd | ||
|
|
1f1cd489be | ||
|
|
2dc094c1db | ||
|
|
9c528d9a8c | ||
|
|
0472b823e7 | ||
|
|
f71cf3cfb4 | ||
|
|
8179ee8196 | ||
|
|
8b8099ad09 | ||
|
|
fd30362267 | ||
|
|
1878f49e8d | ||
|
|
ca0fe5ebae | ||
|
|
ba78f8cc0d | ||
|
|
a614dee5c6 | ||
|
|
463faba9b9 | ||
|
|
9dea8369e3 | ||
|
|
592fb84aa7 | ||
|
|
3019701856 | ||
|
|
1ea70dd3ce | ||
|
|
2a5ad70155 | ||
|
|
9c8e3404bb | ||
|
|
385bbb77a9 | ||
|
|
70f7c06e55 | ||
|
|
369f48ced5 | ||
|
|
7f15f0e15a | ||
|
|
7b48bd44a2 | ||
|
|
7affcd27b7 | ||
|
|
7de9f23226 | ||
|
|
afc35feb8c | ||
|
|
84d682a4a2 | ||
|
|
b29c5bd2fa | ||
|
|
e4c24a511a | ||
|
|
9955a52059 | ||
|
|
de22609196 | ||
|
|
e5de3f4e9d | ||
|
|
660d5b3d4f | ||
|
|
d5eac17097 | ||
|
|
6fe2317681 | ||
|
|
e9e8a2eaaf | ||
|
|
5ec984ac7d | ||
|
|
c98a14e2ac | ||
|
|
bbc656a26c | ||
|
|
722acf0ae7 | ||
|
|
15acd995f9 | ||
|
|
35b90aa746 | ||
|
|
de926211ef | ||
|
|
30e1ea1c6d |
@@ -1,5 +1,6 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
|
||||
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
|
||||
|
||||
# 开发环境配置
|
||||
VITE_APP_ENV = 'development'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
|
||||
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"ShallowRef": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
@@ -35,6 +38,7 @@
|
||||
"createInjectionState": true,
|
||||
"createPinia": true,
|
||||
"createReactiveFn": true,
|
||||
"createRef": true,
|
||||
"createReusableTemplate": true,
|
||||
"createSharedComposable": true,
|
||||
"createTemplatePromise": true,
|
||||
@@ -51,6 +55,7 @@
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"getCurrentWatcher": true,
|
||||
"h": true,
|
||||
"ignorableWatch": true,
|
||||
"inject": true,
|
||||
@@ -60,6 +65,7 @@
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"isShallow": true,
|
||||
"makeDestructurable": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
@@ -103,11 +109,11 @@
|
||||
"refAutoReset": true,
|
||||
"refDebounced": true,
|
||||
"refDefault": true,
|
||||
"refManualReset": true,
|
||||
"refThrottled": true,
|
||||
"refWithControl": true,
|
||||
"resolveComponent": true,
|
||||
"resolveRef": true,
|
||||
"resolveUnref": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
@@ -165,6 +171,7 @@
|
||||
"useCountdown": true,
|
||||
"useCounter": true,
|
||||
"useCssModule": true,
|
||||
"useCssSupports": true,
|
||||
"useCssVar": true,
|
||||
"useCssVars": true,
|
||||
"useCurrentElement": true,
|
||||
@@ -277,6 +284,7 @@
|
||||
"useThrottleFn": true,
|
||||
"useThrottledRefHistory": true,
|
||||
"useTimeAgo": true,
|
||||
"useTimeAgoIntl": true,
|
||||
"useTimeout": true,
|
||||
"useTimeoutFn": true,
|
||||
"useTimeoutPoll": true,
|
||||
|
||||
21
README.md
21
README.md
@@ -1,17 +1,24 @@
|
||||
## 平台简介
|
||||
|
||||
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
|
||||
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
||||
- 配套后端代码仓库地址
|
||||
- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||
- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
|
||||
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)
|
||||
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
|
||||
|
||||
## 配套后端代码仓库地址
|
||||
|
||||
| 介绍 | 项目名 | 项目地址 |
|
||||
|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) |
|
||||
| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
|
||||
|
||||
## 分支说明
|
||||
|
||||
- ts分支(稳定发布主分支 生产可用)
|
||||
- dev分支(开发分支 开发过程中使用)
|
||||
|
||||
## 前端运行
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://gitee.com/JavaLionLi/plus-ui.git
|
||||
|
||||
# 安装依赖
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>RuoYi-Vue-Plus多租户管理系统</title>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
<!--[if lt IE 11
|
||||
]><script>
|
||||
window.location.href = '/html/ie.html';
|
||||
|
||||
92
package.json
92
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "ruoyi-vue-plus",
|
||||
"version": "5.3.1-2.3.0",
|
||||
"version": "5.6.0-2.6.0",
|
||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||
"author": "LionLi",
|
||||
"license": "MIT",
|
||||
@@ -20,69 +20,71 @@
|
||||
"url": "https://gitee.com/JavaLionLi/plus-ui.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@highlightjs/vue-plugin": "2.1.0",
|
||||
"@element-plus/icons-vue": "2.3.2",
|
||||
"@highlightjs/vue-plugin": "2.1.2",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "12.7.0",
|
||||
"@vueuse/core": "14.2.1",
|
||||
"animate.css": "4.1.1",
|
||||
"await-to-js": "3.0.0",
|
||||
"axios": "1.7.8",
|
||||
"axios": "1.13.6",
|
||||
"crypto-js": "4.2.0",
|
||||
"echarts": "5.5.0",
|
||||
"element-plus": "2.8.8",
|
||||
"echarts": "6.0.0",
|
||||
"element-plus": "2.13.5",
|
||||
"file-saver": "2.0.5",
|
||||
"highlight.js": "11.9.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"image-conversion": "2.1.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"jsencrypt": "3.5.4",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.2.6",
|
||||
"pinia": "3.0.4",
|
||||
"screenfull": "6.0.2",
|
||||
"vue": "3.5.13",
|
||||
"vue-cropper": "1.1.1",
|
||||
"vue-i18n": "10.0.5",
|
||||
"vue-json-pretty": "2.4.0",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-types": "5.1.3",
|
||||
"vxe-table": "4.5.22"
|
||||
"vue": "3.5.30",
|
||||
"vue-cropper": "1.1.4",
|
||||
"vue-i18n": "11.3.0",
|
||||
"vue-json-pretty": "2.6.0",
|
||||
"vue-router": "5.0.3",
|
||||
"vue-types": "6.0.0",
|
||||
"vxe-table": "4.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "2.2.276",
|
||||
"@iconify/json": "^2.2.448",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/file-saver": "2.0.7",
|
||||
"@types/js-cookie": "3.0.6",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/node": "^25.4.0",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/preset-attributify": "66.0.0",
|
||||
"@unocss/preset-icons": "66.0.0",
|
||||
"@unocss/preset-uno": "66.0.0",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vue/compiler-sfc": "3.4.23",
|
||||
"@unocss/preset-attributify": "66.6.6",
|
||||
"@unocss/preset-icons": "66.6.6",
|
||||
"@unocss/preset-uno": "66.6.6",
|
||||
"@vitejs/plugin-vue": "6.0.4",
|
||||
"@vue/compiler-sfc": "3.5.30",
|
||||
"@vue/eslint-config-prettier": "10.2.0",
|
||||
"@vue/eslint-config-typescript": "14.4.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"eslint": "9.21.0",
|
||||
"eslint-plugin-prettier": "5.2.3",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"globals": "16.0.0",
|
||||
"prettier": "3.5.2",
|
||||
"sass": "1.84.0",
|
||||
"typescript": "~5.7.3",
|
||||
"unocss": "66.0.0",
|
||||
"unplugin-auto-import": "0.17.5",
|
||||
"unplugin-icons": "0.18.5",
|
||||
"unplugin-vue-components": "28.0.0",
|
||||
"@vue/eslint-config-typescript": "14.6.0",
|
||||
"autoprefixer": "10.4.27",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"eslint-plugin-vue": "9.33.0",
|
||||
"globals": "17.4.0",
|
||||
"prettier": "3.8.1",
|
||||
"sass": "1.98.0",
|
||||
"typescript": "~5.9.3",
|
||||
"unocss": "66.6.6",
|
||||
"unplugin-auto-import": "21.0.0",
|
||||
"unplugin-icons": "23.0.1",
|
||||
"unplugin-vue-components": "31.0.0",
|
||||
"unplugin-vue-setup-extend-plus": "1.0.1",
|
||||
"vite": "5.4.11",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite-plugin-svg-icons-ng": "^1.2.2",
|
||||
"vite-plugin-vue-devtools": "7.7.1",
|
||||
"vitest": "3.0.5",
|
||||
"vue-tsc": "^2.2.2"
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-svg-icons-ng": "^1.5.2",
|
||||
"vite-plugin-vue-devtools": "8.0.7",
|
||||
"vitest": "4.0.18",
|
||||
"vue-tsc": "^3.2.5"
|
||||
},
|
||||
"overrides": {
|
||||
"quill": "1.3.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0",
|
||||
"npm": ">=8.9.0"
|
||||
"node": ">=20.19.0",
|
||||
"npm": ">=8.19.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"Chrome >= 87",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types';
|
||||
import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
|
||||
|
||||
// 查询部门列表
|
||||
export const listDept = (query?: DeptQuery) => {
|
||||
@@ -38,14 +38,6 @@ export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
|
||||
});
|
||||
};
|
||||
|
||||
// 查询部门下拉树结构
|
||||
export const treeselect = (): AxiosPromise<DeptTreeVO[]> => {
|
||||
return request({
|
||||
url: '/system/dept/treeselect',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
// 新增部门
|
||||
export const addDept = (data: DeptForm) => {
|
||||
return request({
|
||||
|
||||
@@ -68,3 +68,11 @@ export const delMenu = (menuId: string | number) => {
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
// 级联删除菜单
|
||||
export const cascadeDelMenu = (menuIds: Array<string | number>) => {
|
||||
return request({
|
||||
url: '/system/menu/cascade/' + menuIds,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import request from '@/utils/request';
|
||||
import { PostForm, PostQuery, PostVO } from './types';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { DeptTreeVO } from '../dept/types';
|
||||
|
||||
// 查询岗位列表
|
||||
export function listPost(query: PostQuery): AxiosPromise<PostVO[]> {
|
||||
@@ -56,3 +57,13 @@ export function delPost(postId: string | number | (string | number)[]) {
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门下拉树结构
|
||||
*/
|
||||
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
|
||||
return request({
|
||||
url: '/system/post/deptTree',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 绑定账号
|
||||
export function authBinding(source: string, tenantId: string) {
|
||||
// 获取跳转URL
|
||||
export function authRouterUrl(source: string, tenantId: string) {
|
||||
return request({
|
||||
url: '/auth/binding/' + source,
|
||||
method: 'get',
|
||||
|
||||
@@ -99,3 +99,11 @@ export function syncTenantDict() {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 同步租户字典
|
||||
export function syncTenantConfig() {
|
||||
return request({
|
||||
url: '/system/tenant/syncTenantConfig',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DeptTreeVO, DeptVO} from './../dept/types';
|
||||
import { DeptTreeVO } from './../dept/types';
|
||||
import { RoleVO } from '@/api/system/role/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
@@ -15,11 +15,12 @@ export interface UserInfo {
|
||||
*/
|
||||
export interface UserQuery extends PageQuery {
|
||||
userName?: string;
|
||||
nickName?: string;
|
||||
phonenumber?: string;
|
||||
status?: string;
|
||||
deptId?: string | number;
|
||||
roleId?: string | number;
|
||||
userIds?: string;
|
||||
userIds?: string | number | (string | number)[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,10 @@ export interface FlowDefinitionForm {
|
||||
flowName: string;
|
||||
flowCode: string;
|
||||
category: string;
|
||||
ext: string;
|
||||
formPath: string;
|
||||
formCustom: string;
|
||||
modelValue: string;
|
||||
}
|
||||
|
||||
export interface definitionXmlVO {
|
||||
|
||||
@@ -31,9 +31,9 @@ export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<FlowInstanc
|
||||
/**
|
||||
* 通过业务id获取历史流程图
|
||||
*/
|
||||
export const flowImage = (businessId: string | number) => {
|
||||
export const flowHisTaskList = (businessId: string | number) => {
|
||||
return request({
|
||||
url: `/workflow/instance/flowImage/${businessId}` + '?t' + Math.random(),
|
||||
url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -87,6 +87,18 @@ export const deleteByInstanceIds = (instanceIds: Array<string | number> | string
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除历史流程实例
|
||||
* @param instanceIds
|
||||
*/
|
||||
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
|
||||
return request({
|
||||
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 作废流程
|
||||
* @param data 参数
|
||||
@@ -99,3 +111,15 @@ export const invalid = (data: any) => {
|
||||
data: data
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 修改流程变量
|
||||
* @param data 参数
|
||||
* @returns
|
||||
*/
|
||||
export const updateVariable = (data: any) => {
|
||||
return request({
|
||||
url: `/workflow/instance/updateVariable`,
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -23,4 +23,6 @@ export interface FlowInstanceVO extends BaseEntity {
|
||||
flowStatus: string;
|
||||
flowStatusName: string;
|
||||
flowTaskList: FlowTaskVO[];
|
||||
businessCode: string;
|
||||
businessTitle: string;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,18 @@ export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交请假并发起流程
|
||||
* @param data
|
||||
*/
|
||||
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
|
||||
return request({
|
||||
url: '/workflow/leave/submitAndFlowStart',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改请假
|
||||
* @param data
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface LeaveVO {
|
||||
id: string | number;
|
||||
applyCode?: string;
|
||||
leaveType: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
@@ -10,6 +11,7 @@ export interface LeaveVO {
|
||||
|
||||
export interface LeaveForm extends BaseEntity {
|
||||
id?: string | number;
|
||||
applyCode?: string;
|
||||
leaveType?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
|
||||
63
src/api/workflow/spel/index.ts
Normal file
63
src/api/workflow/spel/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { SpelVO, SpelForm, SpelQuery } from '@/api/workflow/spel/types';
|
||||
|
||||
/**
|
||||
* 查询流程spel表达式定义列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listSpel = (query?: SpelQuery): AxiosPromise<SpelVO[]> => {
|
||||
return request({
|
||||
url: '/workflow/spel/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询流程spel表达式定义详细
|
||||
* @param id
|
||||
*/
|
||||
export const getSpel = (id: string | number): AxiosPromise<SpelVO> => {
|
||||
return request({
|
||||
url: '/workflow/spel/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增流程spel表达式定义
|
||||
* @param data
|
||||
*/
|
||||
export const addSpel = (data: SpelForm) => {
|
||||
return request({
|
||||
url: '/workflow/spel',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改流程spel表达式定义
|
||||
* @param data
|
||||
*/
|
||||
export const updateSpel = (data: SpelForm) => {
|
||||
return request({
|
||||
url: '/workflow/spel',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除流程spel表达式定义
|
||||
* @param id
|
||||
*/
|
||||
export const delSpel = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/workflow/spel/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
111
src/api/workflow/spel/types.ts
Normal file
111
src/api/workflow/spel/types.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
export interface SpelVO {
|
||||
/**
|
||||
* 主键id
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 组件名称
|
||||
*/
|
||||
componentName: string;
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*/
|
||||
methodName: string;
|
||||
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
methodParams: string;
|
||||
|
||||
/**
|
||||
* 预览spel值
|
||||
*/
|
||||
viewSpel: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface SpelForm extends BaseEntity {
|
||||
/**
|
||||
* 主键id
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 组件名称
|
||||
*/
|
||||
componentName?: string;
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*/
|
||||
methodName?: string;
|
||||
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
methodParams?: string;
|
||||
|
||||
/**
|
||||
* 预览spel值
|
||||
*/
|
||||
viewSpel?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
}
|
||||
|
||||
export interface SpelQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 组件名称
|
||||
*/
|
||||
componentName?: string;
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*/
|
||||
methodName?: string;
|
||||
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
methodParams?: string;
|
||||
|
||||
/**
|
||||
* 预览spel值
|
||||
*/
|
||||
viewSpel?: string;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -148,9 +148,9 @@ export const terminationTask = (data: any) => {
|
||||
* 获取可驳回得任务节点
|
||||
* @returns
|
||||
*/
|
||||
export const getBackTaskNode = (definitionId: string, nodeCode: string) => {
|
||||
export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
|
||||
return request({
|
||||
url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
|
||||
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -191,3 +191,16 @@ export const getNextNodeList = (data: any): any => {
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 催办任务
|
||||
* @param data参数
|
||||
* @returns
|
||||
*/
|
||||
export const urgeTask = (data: any): any => {
|
||||
return request({
|
||||
url: '/workflow/task/urgeTask',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -30,16 +30,20 @@ export interface FlowTaskVO {
|
||||
nodeRatio: string | number;
|
||||
version?: string;
|
||||
applyNode?: boolean;
|
||||
buttonList?: buttonList[];
|
||||
buttonList?: ButtonList[];
|
||||
copyList?: FlowCopyVo[];
|
||||
varList?: Map<string, string>;
|
||||
businessCode: string;
|
||||
businessTitle: string;
|
||||
}
|
||||
|
||||
export interface buttonList {
|
||||
export interface ButtonList {
|
||||
code: string;
|
||||
show: boolean;
|
||||
}
|
||||
export interface VariableVo {
|
||||
key: string;
|
||||
value: string;
|
||||
export interface FlowCopyVo {
|
||||
userId: string | number;
|
||||
nickName: string;
|
||||
}
|
||||
|
||||
export interface TaskOperationBo {
|
||||
@@ -49,6 +53,8 @@ export interface TaskOperationBo {
|
||||
userIds?: string[];
|
||||
//任务ID(必填)
|
||||
taskId: string | number;
|
||||
//消息类型
|
||||
messageType?: string[];
|
||||
//意见或备注信息(可选)
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ export interface StartProcessBo {
|
||||
businessId: string | number;
|
||||
flowCode: string;
|
||||
variables: any;
|
||||
bizExt: any;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding: 14px 36px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--app-radius-md);
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: 600ms ease all;
|
||||
@@ -95,5 +95,5 @@
|
||||
margin: 0;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--app-radius-sm);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
|
||||
.el-dialog {
|
||||
margin: 0 auto !important;
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
overflow: hidden;
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 15px !important;
|
||||
@@ -108,6 +111,8 @@
|
||||
|
||||
// dropdown
|
||||
.el-dropdown-menu {
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
@@ -151,3 +156,122 @@
|
||||
.el-message-box .el-message-box__message {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
}
|
||||
|
||||
.el-message,
|
||||
.el-notification,
|
||||
.el-alert {
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
// Modern rounded inputs
|
||||
.el-input__wrapper,
|
||||
.el-textarea__inner,
|
||||
.el-select__wrapper,
|
||||
.el-date-editor,
|
||||
.el-range-editor,
|
||||
.el-input-number,
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
border-radius: var(--app-radius-md);
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus,
|
||||
.el-textarea__inner:focus,
|
||||
.el-select__wrapper.is-focus,
|
||||
.el-date-editor.is-focus,
|
||||
.el-range-editor.is-focus {
|
||||
box-shadow: 0 0 0 2px var(--el-color-primary-light-8);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.el-button {
|
||||
border-radius: var(--app-radius-md);
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.el-button:not(.is-text):not(.is-link):hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
// Tags and badges
|
||||
.el-tag {
|
||||
border-radius: var(--app-radius-md);
|
||||
}
|
||||
|
||||
// Cards, popovers, drawers
|
||||
.el-popover,
|
||||
.el-tooltip__popper,
|
||||
.el-popper {
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
.el-drawer {
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
}
|
||||
|
||||
.el-drawer__header {
|
||||
margin-bottom: 0;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
|
||||
// Table polish
|
||||
.el-table {
|
||||
border-radius: var(--app-radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.el-table__header-wrapper,
|
||||
.el-table__body-wrapper {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.el-table__row:hover td.el-table__cell {
|
||||
background-color: var(--el-fill-color-light) !important;
|
||||
}
|
||||
|
||||
// Tabs
|
||||
.el-tabs__header {
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
border-radius: var(--app-radius-md);
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
background-color: var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
// Pagination
|
||||
.el-pagination .btn-prev,
|
||||
.el-pagination .btn-next,
|
||||
.el-pagination .el-pager li {
|
||||
border-radius: var(--app-radius-sm);
|
||||
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.el-pagination .el-pager li.is-active {
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
// Breadcrumb
|
||||
.el-breadcrumb {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -14,18 +14,20 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
background: var(--el-bg-color-page);
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
PingFang SC,
|
||||
Hiragino Sans GB,
|
||||
Microsoft YaHei,
|
||||
Arial,
|
||||
'MiSans',
|
||||
'HarmonyOS Sans SC',
|
||||
'PingFang SC',
|
||||
'Source Han Sans SC',
|
||||
'Noto Sans SC',
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -116,11 +118,18 @@ aside {
|
||||
background: #eef1f6;
|
||||
padding: 8px 24px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--app-radius-md);
|
||||
display: block;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
font-family:
|
||||
'MiSans',
|
||||
'HarmonyOS Sans SC',
|
||||
'PingFang SC',
|
||||
'Source Han Sans SC',
|
||||
'Noto Sans SC',
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
sans-serif;
|
||||
color: #2c3e50;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -139,21 +148,27 @@ aside {
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background: var(--app-surface-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
}
|
||||
|
||||
// search面板样式
|
||||
.panel,
|
||||
.search {
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
border-radius: var(--app-radius-lg);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
padding: 0.75rem;
|
||||
transition: all ease 0.3s;
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 12px #0000001a;
|
||||
transition: all ease 0.3s;
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: var(--el-border-color);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ h6 {
|
||||
}
|
||||
|
||||
.el-form .el-form-item__label {
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
}
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
margin-top: 6vh !important;
|
||||
@@ -127,7 +127,7 @@ h6 {
|
||||
margin-top: 5px;
|
||||
border: 1px solid #e5e6e7;
|
||||
background: #ffffff none;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--app-radius-md);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -190,12 +190,27 @@ h6 {
|
||||
.el-card__header {
|
||||
padding: 14px 15px 7px !important;
|
||||
min-height: 40px;
|
||||
background: var(--el-fill-color-blank);
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 15px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: var(--el-border-color-lighter);
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.el-card:hover {
|
||||
box-shadow: var(--app-shadow-md);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.card-box {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -282,3 +297,9 @@ h6 {
|
||||
.top-right-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* horizontal el menu */
|
||||
.el-menu--horizontal .el-menu-item .svg-icon + span,
|
||||
.el-menu--horizontal .el-sub-menu__title .svg-icon + span {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
transition: margin-left 0.28s;
|
||||
margin-left: $base-sidebar-width;
|
||||
position: relative;
|
||||
background: var(--el-bg-color-page);
|
||||
}
|
||||
|
||||
.sidebarHide {
|
||||
@@ -26,7 +27,7 @@
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
-webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
// reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
@@ -65,7 +66,7 @@
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin-right: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
@@ -88,12 +89,16 @@
|
||||
// menu hover
|
||||
.theme-dark .sub-menu-title-noDropdown,
|
||||
.theme-dark .el-sub-menu__title {
|
||||
border-radius: var(--app-radius-md);
|
||||
margin: 1px 5px 1px 5px;
|
||||
&:hover {
|
||||
background-color: $base-sub-menu-title-hover !important;
|
||||
}
|
||||
}
|
||||
.sub-menu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
border-radius: var(--app-radius-md);
|
||||
margin: 1px 5px 1px 5px;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
@@ -105,8 +110,11 @@
|
||||
|
||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .el-sub-menu .el-menu-item {
|
||||
min-width: $base-sidebar-width !important;
|
||||
&:hover {
|
||||
min-width: calc($base-sidebar-width - 20px) !important;
|
||||
border-radius: var(--app-radius-md);
|
||||
height: 45px;
|
||||
margin: 1px 5px 1px 5px;
|
||||
&:not(.is-active):hover {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
}
|
||||
@@ -114,28 +122,54 @@
|
||||
& .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;
|
||||
border-radius: var(--app-radius-md);
|
||||
height: 45px;
|
||||
margin: 1px 5px 1px 5px;
|
||||
|
||||
&:hover {
|
||||
&.is-active {
|
||||
background-color: var(--el-menu-active-color) !important;
|
||||
color: #fff;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: $base-sub-menu-hover !important;
|
||||
}
|
||||
}
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .theme-dark .el-menu-item {
|
||||
&:hover {
|
||||
border-radius: var(--app-radius-md);
|
||||
height: 45px;
|
||||
margin: 1px 5px 1px 5px;
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--el-menu-active-color) !important;
|
||||
color: #fff;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: $base-menu-hover !important;
|
||||
}
|
||||
}
|
||||
|
||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .el-menu-item {
|
||||
&:hover {
|
||||
border-radius: var(--app-radius-md);
|
||||
height: 45px;
|
||||
margin: 1px 5px 1px 5px;
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--el-menu-active-color) !important;
|
||||
color: #fff;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: rgba(0, 0, 0, 0.04) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收起菜单后的样式
|
||||
.hideSidebar {
|
||||
.sidebar-container {
|
||||
width: 54px !important;
|
||||
@@ -148,29 +182,48 @@
|
||||
.sub-menu-title-noDropdown {
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
height: 45px;
|
||||
// 选中状态的菜单
|
||||
&.is-active {
|
||||
background-color: var(--el-menu-active-color) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.el-tooltip {
|
||||
padding: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
|
||||
& .el-sub-menu {
|
||||
overflow: hidden;
|
||||
border-radius: var(--app-radius-md);
|
||||
.el-sub-menu__title.el-tooltip__trigger {
|
||||
border-radius: var(--app-radius-md);
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
// 选中状态的菜单
|
||||
&.is-active .el-sub-menu__title.el-tooltip__trigger {
|
||||
background-color: var(--el-menu-active-color) !important;
|
||||
}
|
||||
|
||||
|
||||
& > .el-sub-menu__title {
|
||||
padding: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse {
|
||||
.is-active .svg-icon {
|
||||
fill: #fff;
|
||||
}
|
||||
.svg-icon {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
// 这里设置width会跟随sidebar-container的transition 不符合预期
|
||||
}
|
||||
.el-sub-menu {
|
||||
& > .el-sub-menu__title {
|
||||
& > span {
|
||||
@@ -232,3 +285,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
// 收起菜单后悬浮的菜单样式
|
||||
.el-popper.is-pure{
|
||||
border-radius: var(--app-radius-md);
|
||||
.el-menu--popup{
|
||||
border-radius: var(--app-radius-md);
|
||||
}
|
||||
.el-menu-item{
|
||||
border-radius: var(--app-radius-sm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
transition: opacity 0.28s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-enter-from,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 全局SCSS变量
|
||||
:root {
|
||||
--menuBg: #304156;
|
||||
--menuBg: #1f2d3d;
|
||||
--menuColor: #bfcbd9;
|
||||
--menuActiveText: #f4f4f5;
|
||||
--menuHover: #263445;
|
||||
@@ -10,6 +10,11 @@
|
||||
--subMenuHover: #001528;
|
||||
--subMenuTitleHover: #293444;
|
||||
|
||||
// 菜单栏缩进
|
||||
--el-menu-base-level-padding: 12px;
|
||||
--el-menu-level-padding: 8px;
|
||||
--el-menu-item-height: 50px;
|
||||
|
||||
--fixedHeaderBg: #ffffff;
|
||||
--tableHeaderBg: #f8f8f9;
|
||||
--tableHeaderTextColor: #515a6e;
|
||||
@@ -20,6 +25,24 @@
|
||||
// 添加 tag 相关变量
|
||||
--tags-view-active-bg: var(--el-color-primary);
|
||||
--tags-view-active-border-color: var(--el-color-primary);
|
||||
|
||||
// Modern rounded style + soft shadows
|
||||
--app-radius-sm: 6px;
|
||||
--app-radius-md: 10px;
|
||||
--app-radius-lg: 14px;
|
||||
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
|
||||
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16);
|
||||
--app-surface-bg: #ffffff;
|
||||
--app-surface-border: var(--el-border-color-lighter);
|
||||
|
||||
// Element Plus tokens
|
||||
--el-border-radius-base: var(--app-radius-md);
|
||||
--el-border-radius-small: var(--app-radius-sm);
|
||||
--el-border-radius-round: 999px;
|
||||
--el-box-shadow-light: var(--app-shadow-sm);
|
||||
--el-box-shadow: var(--app-shadow-md);
|
||||
--el-bg-color-page: #f5f7fb;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
@@ -43,8 +66,103 @@ html.dark {
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
--el-button-bg-color: var(--el-color-primary-dark-6);
|
||||
--el-button-border-color: var(--el-color-primary-light-2);
|
||||
--el-button-bg-color: #2b6bd3;
|
||||
--el-button-border-color: #3a7be8;
|
||||
--el-button-text-color: #eef4ff;
|
||||
--el-button-hover-bg-color: #3a7be8;
|
||||
--el-button-hover-border-color: #3a7be8;
|
||||
--el-button-active-bg-color: #255fb8;
|
||||
--el-button-active-border-color: #255fb8;
|
||||
}
|
||||
|
||||
.el-button--primary.is-plain {
|
||||
--el-button-bg-color: rgba(43, 107, 211, 0.12);
|
||||
--el-button-border-color: rgba(58, 123, 232, 0.5);
|
||||
--el-button-text-color: #dbe8ff;
|
||||
--el-button-hover-bg-color: rgba(58, 123, 232, 0.2);
|
||||
--el-button-hover-border-color: rgba(58, 123, 232, 0.7);
|
||||
--el-button-active-bg-color: rgba(43, 107, 211, 0.28);
|
||||
--el-button-active-border-color: rgba(43, 107, 211, 0.8);
|
||||
}
|
||||
|
||||
.el-button--success {
|
||||
--el-button-bg-color: #1f8a5a;
|
||||
--el-button-border-color: #29a46d;
|
||||
--el-button-text-color: #eefaf4;
|
||||
--el-button-hover-bg-color: #29a46d;
|
||||
--el-button-hover-border-color: #29a46d;
|
||||
--el-button-active-bg-color: #1b784f;
|
||||
--el-button-active-border-color: #1b784f;
|
||||
}
|
||||
|
||||
.el-button--success.is-plain {
|
||||
--el-button-bg-color: rgba(31, 138, 90, 0.12);
|
||||
--el-button-border-color: rgba(41, 164, 109, 0.5);
|
||||
--el-button-text-color: #dbf6e8;
|
||||
--el-button-hover-bg-color: rgba(41, 164, 109, 0.2);
|
||||
--el-button-hover-border-color: rgba(41, 164, 109, 0.7);
|
||||
--el-button-active-bg-color: rgba(31, 138, 90, 0.28);
|
||||
--el-button-active-border-color: rgba(31, 138, 90, 0.8);
|
||||
}
|
||||
|
||||
.el-button--warning {
|
||||
--el-button-bg-color: #b87922;
|
||||
--el-button-border-color: #d6953b;
|
||||
--el-button-text-color: #fff7e6;
|
||||
--el-button-hover-bg-color: #d6953b;
|
||||
--el-button-hover-border-color: #d6953b;
|
||||
--el-button-active-bg-color: #a56c1d;
|
||||
--el-button-active-border-color: #a56c1d;
|
||||
}
|
||||
|
||||
.el-button--warning.is-plain {
|
||||
--el-button-bg-color: rgba(184, 121, 34, 0.12);
|
||||
--el-button-border-color: rgba(214, 149, 59, 0.5);
|
||||
--el-button-text-color: #ffecc8;
|
||||
--el-button-hover-bg-color: rgba(214, 149, 59, 0.2);
|
||||
--el-button-hover-border-color: rgba(214, 149, 59, 0.7);
|
||||
--el-button-active-bg-color: rgba(184, 121, 34, 0.28);
|
||||
--el-button-active-border-color: rgba(184, 121, 34, 0.8);
|
||||
}
|
||||
|
||||
.el-button--danger {
|
||||
--el-button-bg-color: #b24a4a;
|
||||
--el-button-border-color: #d16060;
|
||||
--el-button-text-color: #ffecec;
|
||||
--el-button-hover-bg-color: #d16060;
|
||||
--el-button-hover-border-color: #d16060;
|
||||
--el-button-active-bg-color: #9c3f3f;
|
||||
--el-button-active-border-color: #9c3f3f;
|
||||
}
|
||||
|
||||
.el-button--danger.is-plain {
|
||||
--el-button-bg-color: rgba(178, 74, 74, 0.12);
|
||||
--el-button-border-color: rgba(209, 96, 96, 0.5);
|
||||
--el-button-text-color: #ffd6d6;
|
||||
--el-button-hover-bg-color: rgba(209, 96, 96, 0.2);
|
||||
--el-button-hover-border-color: rgba(209, 96, 96, 0.7);
|
||||
--el-button-active-bg-color: rgba(178, 74, 74, 0.28);
|
||||
--el-button-active-border-color: rgba(178, 74, 74, 0.8);
|
||||
}
|
||||
|
||||
.el-button--info {
|
||||
--el-button-bg-color: #4b5563;
|
||||
--el-button-border-color: #667085;
|
||||
--el-button-text-color: #f3f4f6;
|
||||
--el-button-hover-bg-color: #667085;
|
||||
--el-button-hover-border-color: #667085;
|
||||
--el-button-active-bg-color: #3f4753;
|
||||
--el-button-active-border-color: #3f4753;
|
||||
}
|
||||
|
||||
.el-button--info.is-plain {
|
||||
--el-button-bg-color: rgba(75, 85, 99, 0.16);
|
||||
--el-button-border-color: rgba(102, 112, 133, 0.55);
|
||||
--el-button-text-color: #e5e7eb;
|
||||
--el-button-hover-bg-color: rgba(102, 112, 133, 0.22);
|
||||
--el-button-hover-border-color: rgba(102, 112, 133, 0.75);
|
||||
--el-button-active-bg-color: rgba(75, 85, 99, 0.3);
|
||||
--el-button-active-border-color: rgba(75, 85, 99, 0.85);
|
||||
}
|
||||
|
||||
.el-switch {
|
||||
@@ -57,9 +175,41 @@ html.dark {
|
||||
--el-tag-border-color: var(--el-color-primary-light-2);
|
||||
}
|
||||
|
||||
.el-tag--success {
|
||||
--el-tag-bg-color: rgba(31, 138, 90, 0.18);
|
||||
--el-tag-border-color: rgba(41, 164, 109, 0.6);
|
||||
--el-tag-text-color: #c7f2df;
|
||||
}
|
||||
|
||||
.el-tag--warning {
|
||||
--el-tag-bg-color: rgba(184, 121, 34, 0.18);
|
||||
--el-tag-border-color: rgba(214, 149, 59, 0.6);
|
||||
--el-tag-text-color: #ffe6bb;
|
||||
}
|
||||
|
||||
.el-tag--danger {
|
||||
--el-tag-bg-color: rgba(178, 74, 74, 0.18);
|
||||
--el-tag-border-color: rgba(209, 96, 96, 0.6);
|
||||
--el-tag-text-color: #ffd0d0;
|
||||
}
|
||||
|
||||
.el-tag--info {
|
||||
--el-tag-bg-color: rgba(75, 85, 99, 0.18);
|
||||
--el-tag-border-color: rgba(102, 112, 133, 0.6);
|
||||
--el-tag-text-color: #e5e7eb;
|
||||
}
|
||||
|
||||
// 在深色模式下使用更深的颜色
|
||||
--tags-view-active-bg: var(--el-color-primary-dark-6);
|
||||
--tags-view-active-border-color: var(--el-color-primary-light-2);
|
||||
|
||||
// Modern rounded style + soft shadows (dark)
|
||||
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28), 0 8px 18px rgba(0, 0, 0, 0.25);
|
||||
--app-shadow-md: 0 10px 26px rgba(0, 0, 0, 0.35);
|
||||
--app-shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.4);
|
||||
--app-surface-bg: #151922;
|
||||
--app-surface-border: var(--el-border-color);
|
||||
--el-bg-color-page: #0f1115;
|
||||
// vxe-table 主题
|
||||
--vxe-font-color: #98989e;
|
||||
--vxe-primary-color: #2c7ecf;
|
||||
@@ -132,4 +282,4 @@ $base-sidebar-width: 200px;
|
||||
dangerColor: $--color-danger;
|
||||
infoColor: $--color-info;
|
||||
warningColor: $--color-warning;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const findPathNum = (str, char = '/') => {
|
||||
return str.split(char).length - 1;
|
||||
};
|
||||
const getMatched = (pathList, routeList, matched) => {
|
||||
let data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
|
||||
const data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
|
||||
if (data) {
|
||||
matched.push(data);
|
||||
if (data.children && pathList.length) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="(item, index) in options">
|
||||
<template v-if="values.includes(item.value)">
|
||||
<template v-if="isValueMatch(item.value)">
|
||||
<span
|
||||
v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
|
||||
:key="item.value"
|
||||
@@ -50,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const values = computed(() => {
|
||||
if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
|
||||
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value]
|
||||
return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);
|
||||
});
|
||||
|
||||
@@ -58,7 +59,7 @@ const unmatch = computed(() => {
|
||||
// 传入值为非数组
|
||||
let unmatch = false; // 添加一个标志来判断是否有未匹配项
|
||||
values.value.forEach((item) => {
|
||||
if (!props.options.some((v) => v.value === item)) {
|
||||
if (!props.options.some((v) => v.value == item)) {
|
||||
unmatch = true; // 如果有未匹配项,将标志设置为true
|
||||
}
|
||||
});
|
||||
@@ -85,6 +86,10 @@ const handleArray = (array: Array<string | number>) => {
|
||||
return pre + ' ' + cur;
|
||||
});
|
||||
};
|
||||
|
||||
const isValueMatch = (itemValue: any) => {
|
||||
return values.value.some(val => val == itemValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
v-if="type === 'url'"
|
||||
:action="upload.url"
|
||||
v-if="type"
|
||||
action=""
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:http-request="handleUploadRequest"
|
||||
class="editor-img-uploader"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="upload.headers"
|
||||
>
|
||||
<i ref="uploadRef"></i>
|
||||
</el-upload>
|
||||
@@ -31,7 +29,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
|
||||
import { QuillEditor, Quill } from '@vueup/vue-quill';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import type { UploadRequestHandler, UploadRequestOptions } from 'element-plus';
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
@@ -47,15 +45,11 @@ const props = defineProps({
|
||||
/* 上传文件大小限制(MB) */
|
||||
fileSize: propTypes.number.def(5),
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: propTypes.string.def('url')
|
||||
type: propTypes.string.def('base64')
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const upload = reactive<UploadOption>({
|
||||
headers: globalHeaders(),
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
|
||||
});
|
||||
const quillEditorRef = ref();
|
||||
const uploadRef = ref<HTMLDivElement>();
|
||||
|
||||
@@ -95,7 +89,7 @@ const options = ref<any>({
|
||||
});
|
||||
|
||||
const styles = computed(() => {
|
||||
let style: any = {};
|
||||
const style: any = {};
|
||||
if (props.minHeight) {
|
||||
style.minHeight = `${props.minHeight}px`;
|
||||
}
|
||||
@@ -116,28 +110,9 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 图片上传成功返回图片地址
|
||||
const handleUploadSuccess = (res: any) => {
|
||||
// 如果上传成功
|
||||
if (res.code === 200) {
|
||||
// 获取富文本实例
|
||||
let quill = toRaw(quillEditorRef.value).getQuill();
|
||||
// 获取光标位置
|
||||
let length = quill.selection.savedRange.index;
|
||||
// 插入图片,res为服务器返回的图片链接地址
|
||||
quill.insertEmbed(length, 'image', res.data.url);
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1);
|
||||
proxy?.$modal.closeLoading();
|
||||
} else {
|
||||
proxy?.$modal.msgError('图片插入失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传前拦截
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
const isJPG = type.includes(file.type);
|
||||
//检验文件格式
|
||||
if (!isJPG) {
|
||||
@@ -156,9 +131,41 @@ const handleBeforeUpload = (file: any) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
// 图片失败拦截
|
||||
const handleUploadError = (err: any) => {
|
||||
proxy?.$modal.msgError('上传文件失败');
|
||||
// base64 模式插入图片
|
||||
const handleUploadRequest: UploadRequestHandler = (options: UploadRequestOptions) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const file = options.file as File;
|
||||
const quill = toRaw(quillEditorRef.value)?.getQuill();
|
||||
if (!quill) {
|
||||
proxy?.$modal.msgError('编辑器未就绪');
|
||||
proxy?.$modal.closeLoading();
|
||||
reject(new Error('editor not ready'));
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result as string;
|
||||
const range = quill.selection?.savedRange;
|
||||
const length = range ? range.index : quill.getLength();
|
||||
quill.insertEmbed(length, 'image', base64);
|
||||
quill.setSelection(length + 1);
|
||||
proxy?.$modal.closeLoading();
|
||||
options.onSuccess?.({ url: base64 });
|
||||
resolve();
|
||||
};
|
||||
reader.onerror = () => {
|
||||
proxy?.$modal.msgError('图片插入失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
const err = Object.assign(new Error('read image failed'), {
|
||||
status: 0,
|
||||
method: 'POST',
|
||||
url: options.action || ''
|
||||
});
|
||||
options.onError?.(err as any);
|
||||
reject(err);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
|
||||
|
||||
// 删除文件
|
||||
const handleDelete = (index: number) => {
|
||||
let ossId = fileList.value[index].ossId;
|
||||
const ossId = fileList.value[index].ossId;
|
||||
delOss(ossId);
|
||||
fileList.value.splice(index, 1);
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
|
||||
@@ -27,7 +27,7 @@ const realSrc = computed(() => {
|
||||
if (!props.src) {
|
||||
return;
|
||||
}
|
||||
let real_src = props.src.split(',')[0];
|
||||
const real_src = props.src.split(',')[0];
|
||||
return real_src;
|
||||
});
|
||||
|
||||
@@ -35,8 +35,8 @@ const realSrcList = computed(() => {
|
||||
if (!props.src) {
|
||||
return [];
|
||||
}
|
||||
let real_src_list = props.src.split(',');
|
||||
let srcList: string[] = [];
|
||||
const real_src_list = props.src.split(',');
|
||||
const srcList: string[] = [];
|
||||
real_src_list.forEach((item: string) => {
|
||||
if (item.trim() === '') {
|
||||
return;
|
||||
|
||||
@@ -189,7 +189,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
|
||||
const handleDelete = (file: UploadFile): boolean => {
|
||||
const findex = fileList.value.map((f) => f.name).indexOf(file.name);
|
||||
if (findex > -1 && uploadList.value.length === number.value) {
|
||||
let ossId = fileList.value[findex].ossId;
|
||||
const ossId = fileList.value[findex].ossId;
|
||||
delOss(ossId);
|
||||
fileList.value.splice(findex, 1);
|
||||
emit('update:modelValue', listToString(fileList.value));
|
||||
@@ -225,7 +225,7 @@ const handlePictureCardPreview = (file: any) => {
|
||||
const listToString = (list: any[], separator?: string) => {
|
||||
let strs = '';
|
||||
separator = separator || ',';
|
||||
for (let i in list) {
|
||||
for (const i in list) {
|
||||
if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
|
||||
strs += list[i].ossId + separator;
|
||||
}
|
||||
|
||||
100
src/components/Process/MessageType.vue
Normal file
100
src/components/Process/MessageType.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="props.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false">
|
||||
<el-form v-loading="loading" ref="ruleFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="消息提醒" prop="messageType">
|
||||
<el-checkbox-group v-model="form.messageType">
|
||||
<el-checkbox value="1" name="type" disabled>站内信</el-checkbox>
|
||||
<el-checkbox value="2" name="type">邮件</el-checkbox>
|
||||
<el-checkbox value="3" name="type">短信</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="消息内容" prop="message">
|
||||
<el-input v-model="form.message" type="textarea" resize="none" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer" style="float: right; padding-bottom: 20px">
|
||||
<el-button :disabled="buttonDisabled" type="primary" @click="submit(ruleFormRef)">确认</el-button>
|
||||
<el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ComponentInternalInstance } from 'vue';
|
||||
import { ElForm, FormInstance } from 'element-plus';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const emits = defineEmits(['submitCallback', 'cancelCallback']);
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '提示'
|
||||
}
|
||||
});
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
//遮罩层
|
||||
const loading = ref(true);
|
||||
const visible = ref(false);
|
||||
const buttonDisabled = ref(true);
|
||||
const form = ref<Record<string, any>>({
|
||||
message: undefined,
|
||||
messageType: ['1']
|
||||
});
|
||||
const rules = reactive<Record<string, any>>({
|
||||
messageType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择消息提醒',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
message: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入消息内容',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
//确认
|
||||
//打开弹窗
|
||||
const open = async () => {
|
||||
reset();
|
||||
visible.value = true;
|
||||
loading.value = false;
|
||||
buttonDisabled.value = false;
|
||||
};
|
||||
//关闭弹窗
|
||||
const close = async () => {
|
||||
reset();
|
||||
visible.value = false;
|
||||
};
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
emits('submitCallback', form.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
//取消
|
||||
const cancel = async () => {
|
||||
visible.value = false;
|
||||
buttonDisabled.value = false;
|
||||
emits('cancelCallback');
|
||||
};
|
||||
//重置
|
||||
const reset = async () => {
|
||||
form.value.taskIdList = [];
|
||||
form.value.message = '';
|
||||
form.value.messageType = ['1'];
|
||||
};
|
||||
/**
|
||||
* 对外暴露子组件方法
|
||||
*/
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
57
src/components/Process/approvalButton.vue
Normal file
57
src/components/Process/approvalButton.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div>
|
||||
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft', mode)">暂存</el-button>
|
||||
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit', mode)">提 交</el-button>
|
||||
<el-button v-if="approvalButtonShow" :loading="props.buttonLoading" type="primary" @click="approvalVerifyOpen">审批</el-button>
|
||||
<el-button v-if="props.id && props.status !== 'draft'" type="primary" @click="handleApprovalRecord">流程进度</el-button>
|
||||
<slot />
|
||||
</div>
|
||||
<div>
|
||||
<el-button style="float: right" @click="goBack()">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const props = defineProps({
|
||||
status: propTypes.string.def(''),
|
||||
pageType: propTypes.string.def(''),
|
||||
buttonLoading: propTypes.bool.def(false),
|
||||
id: propTypes.string.def('') || propTypes.number.def(),
|
||||
mode: propTypes.bool.def(false)
|
||||
});
|
||||
const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
|
||||
//暂存,提交
|
||||
const submitForm = async (type, mode) => {
|
||||
emits('submitForm', type, mode);
|
||||
};
|
||||
//审批
|
||||
const approvalVerifyOpen = async () => {
|
||||
emits('approvalVerifyOpen');
|
||||
};
|
||||
//审批记录
|
||||
const handleApprovalRecord = () => {
|
||||
emits('handleApprovalRecord');
|
||||
};
|
||||
|
||||
//校验提交按钮是否显示
|
||||
const submitButtonShow = computed(() => {
|
||||
return (
|
||||
props.pageType === 'add' ||
|
||||
(props.pageType === 'update' && props.status && (props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
|
||||
);
|
||||
});
|
||||
|
||||
//校验审批按钮是否显示
|
||||
const approvalButtonShow = computed(() => {
|
||||
return props.pageType === 'approval' && props.status && props.status === 'waiting';
|
||||
});
|
||||
|
||||
//返回
|
||||
const goBack = () => {
|
||||
proxy.$tab.closePage(proxy.$route);
|
||||
proxy.$router.go(-1);
|
||||
};
|
||||
</script>
|
||||
@@ -3,21 +3,7 @@
|
||||
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false">
|
||||
<el-tabs v-model="tabActiveName" class="demo-tabs">
|
||||
<el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh">
|
||||
<div
|
||||
ref="imageWrapperRef"
|
||||
class="image-wrapper"
|
||||
@wheel="handleMouseWheel"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@dblclick="resetTransform"
|
||||
:style="transformStyle"
|
||||
>
|
||||
<el-card class="box-card">
|
||||
<el-image :src="imgUrl" class="scalable-image" />
|
||||
</el-card>
|
||||
</div>
|
||||
<flowChart :ins-id="insId" v-if="insId" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-loading="loading" label="审批信息" name="info">
|
||||
<div>
|
||||
@@ -73,10 +59,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { flowImage } from '@/api/workflow/instance';
|
||||
import { flowHisTaskList } from '@/api/workflow/instance';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
|
||||
import FlowChart from '@/components/Process/flowChart.vue';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
|
||||
const props = defineProps({
|
||||
@@ -87,7 +73,7 @@ const loading = ref(false);
|
||||
const visible = ref(false);
|
||||
const historyList = ref<Array<any>>([]);
|
||||
const tabActiveName = ref('image');
|
||||
const imgUrl = ref('');
|
||||
const insId = ref(null);
|
||||
|
||||
//初始化查询审批记录
|
||||
const init = async (businessId: string | number) => {
|
||||
@@ -95,10 +81,10 @@ const init = async (businessId: string | number) => {
|
||||
loading.value = true;
|
||||
tabActiveName.value = 'image';
|
||||
historyList.value = [];
|
||||
flowImage(businessId).then((resp) => {
|
||||
flowHisTaskList(businessId).then((resp) => {
|
||||
if (resp.data) {
|
||||
historyList.value = resp.data.list;
|
||||
imgUrl.value = 'data:image/gif;base64,' + resp.data.image;
|
||||
insId.value = resp.data.instanceId;
|
||||
if (historyList.value.length > 0) {
|
||||
historyList.value.forEach((item) => {
|
||||
if (item.ext) {
|
||||
@@ -124,109 +110,6 @@ const handleDownload = (ossId: string) => {
|
||||
proxy?.$download.oss(ossId);
|
||||
};
|
||||
|
||||
const imageWrapperRef = ref<HTMLElement | null>(null);
|
||||
const scale = ref(1); // 初始缩放比例
|
||||
const maxScale = 3; // 最大缩放比例
|
||||
const minScale = 0.5; // 最小缩放比例
|
||||
|
||||
let isDragging = false;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let currentTranslateX = 0;
|
||||
let currentTranslateY = 0;
|
||||
|
||||
const handleMouseWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
let newScale = scale.value - event.deltaY / 1000;
|
||||
newScale = Math.max(minScale, Math.min(newScale, maxScale));
|
||||
if (newScale !== scale.value) {
|
||||
scale.value = newScale;
|
||||
resetDragPosition(); // 重置拖拽位置,使图片居中
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
if (scale.value > 1) {
|
||||
event.preventDefault(); // 阻止默认行为,防止拖拽
|
||||
isDragging = true;
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !imageWrapperRef.value) return;
|
||||
|
||||
const deltaX = event.clientX - startX;
|
||||
const deltaY = event.clientY - startY;
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
|
||||
currentTranslateX += deltaX;
|
||||
currentTranslateY += deltaY;
|
||||
|
||||
// 边界检测,防止图片被拖出容器
|
||||
const bounds = getBounds();
|
||||
if (currentTranslateX > bounds.maxTranslateX) {
|
||||
currentTranslateX = bounds.maxTranslateX;
|
||||
} else if (currentTranslateX < bounds.minTranslateX) {
|
||||
currentTranslateX = bounds.minTranslateX;
|
||||
}
|
||||
|
||||
if (currentTranslateY > bounds.maxTranslateY) {
|
||||
currentTranslateY = bounds.maxTranslateY;
|
||||
} else if (currentTranslateY < bounds.minTranslateY) {
|
||||
currentTranslateY = bounds.minTranslateY;
|
||||
}
|
||||
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const resetTransform = () => {
|
||||
scale.value = 1;
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const resetDragPosition = () => {
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const applyTransform = () => {
|
||||
if (imageWrapperRef.value) {
|
||||
imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
|
||||
}
|
||||
};
|
||||
|
||||
const getBounds = () => {
|
||||
if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
|
||||
|
||||
const imgRect = imageWrapperRef.value.getBoundingClientRect();
|
||||
const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
|
||||
|
||||
const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
|
||||
const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
|
||||
const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
|
||||
const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
|
||||
|
||||
return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
|
||||
};
|
||||
|
||||
const transformStyle = computed(() => ({
|
||||
transition: isDragging ? 'none' : 'transform 0.2s ease'
|
||||
}));
|
||||
|
||||
/**
|
||||
* 对外暴露子组件方法
|
||||
*/
|
||||
@@ -235,46 +118,10 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.triangle {
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.triangle::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 8em;
|
||||
right: 215px;
|
||||
border: 15px solid;
|
||||
border-color: transparent #fff transparent transparent;
|
||||
}
|
||||
|
||||
.container {
|
||||
:deep(.el-dialog .el-dialog__body) {
|
||||
max-height: calc(100vh - 170px) !important;
|
||||
min-height: calc(100vh - 170px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none; /* 禁用文本选择 */
|
||||
cursor: grab; /* 设置初始鼠标指针为可拖动 */
|
||||
}
|
||||
|
||||
.image-wrapper:active {
|
||||
cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
|
||||
}
|
||||
|
||||
.scalable-image {
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
40
src/components/Process/flowChart.vue
Normal file
40
src/components/Process/flowChart.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="height: 68vh" class="iframe-wrapper">
|
||||
<iframe :src="iframeUrl" style="width: 100%; height: 100%" frameborder="0" scrolling="no" class="custom-iframe" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getToken } from '@/utils/auth';
|
||||
|
||||
// Props 定义方式变化
|
||||
const props = defineProps({
|
||||
insId: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const iframeUrl = ref('');
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
|
||||
onMounted(async () => {
|
||||
const url = baseUrl + `/warm-flow-ui/index.html?id=${props.insId}&type=FlowChart&t=${Date.now()}`;
|
||||
iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.iframe-wrapper {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-iframe {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
154
src/components/Process/flowChartImg.vue
Normal file
154
src/components/Process/flowChartImg.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div
|
||||
ref="imageWrapperRef"
|
||||
class="image-wrapper"
|
||||
@wheel="handleMouseWheel"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@dblclick="resetTransform"
|
||||
:style="transformStyle"
|
||||
>
|
||||
<el-card class="box-card">
|
||||
<el-image :src="props.imgUrl" class="scalable-image" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Props 定义方式变化
|
||||
const props = defineProps({
|
||||
imgUrl: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
});
|
||||
|
||||
const imageWrapperRef = ref<HTMLElement | null>(null);
|
||||
const scale = ref(1); // 初始缩放比例
|
||||
const maxScale = 3; // 最大缩放比例
|
||||
const minScale = 0.5; // 最小缩放比例
|
||||
|
||||
let isDragging = false;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let currentTranslateX = 0;
|
||||
let currentTranslateY = 0;
|
||||
|
||||
const handleMouseWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
let newScale = scale.value - event.deltaY / 1000;
|
||||
newScale = Math.max(minScale, Math.min(newScale, maxScale));
|
||||
if (newScale !== scale.value) {
|
||||
scale.value = newScale;
|
||||
resetDragPosition(); // 重置拖拽位置,使图片居中
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
if (scale.value > 1) {
|
||||
event.preventDefault(); // 阻止默认行为,防止拖拽
|
||||
isDragging = true;
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !imageWrapperRef.value) return;
|
||||
|
||||
const deltaX = event.clientX - startX;
|
||||
const deltaY = event.clientY - startY;
|
||||
startX = event.clientX;
|
||||
startY = event.clientY;
|
||||
|
||||
currentTranslateX += deltaX;
|
||||
currentTranslateY += deltaY;
|
||||
|
||||
// 边界检测,防止图片被拖出容器
|
||||
const bounds = getBounds();
|
||||
if (currentTranslateX > bounds.maxTranslateX) {
|
||||
currentTranslateX = bounds.maxTranslateX;
|
||||
} else if (currentTranslateX < bounds.minTranslateX) {
|
||||
currentTranslateX = bounds.minTranslateX;
|
||||
}
|
||||
|
||||
if (currentTranslateY > bounds.maxTranslateY) {
|
||||
currentTranslateY = bounds.maxTranslateY;
|
||||
} else if (currentTranslateY < bounds.minTranslateY) {
|
||||
currentTranslateY = bounds.minTranslateY;
|
||||
}
|
||||
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const resetTransform = () => {
|
||||
scale.value = 1;
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const resetDragPosition = () => {
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
applyTransform();
|
||||
};
|
||||
|
||||
const applyTransform = () => {
|
||||
if (imageWrapperRef.value) {
|
||||
imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
|
||||
}
|
||||
};
|
||||
|
||||
const getBounds = () => {
|
||||
if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
|
||||
|
||||
const imgRect = imageWrapperRef.value.getBoundingClientRect();
|
||||
const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
|
||||
|
||||
const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
|
||||
const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
|
||||
const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
|
||||
const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
|
||||
|
||||
return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
|
||||
};
|
||||
|
||||
const transformStyle = computed(() => ({
|
||||
transition: isDragging ? 'none' : 'transform 0.2s ease'
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none; /* 禁用文本选择 */
|
||||
cursor: grab; /* 设置初始鼠标指针为可拖动 */
|
||||
}
|
||||
|
||||
.image-wrapper:active {
|
||||
cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
|
||||
}
|
||||
|
||||
.scalable-image {
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -113,7 +113,8 @@ const handleTransferTask = async (data) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userId: data[0].userId,
|
||||
taskId: task.value.id,
|
||||
message: ''
|
||||
message: '',
|
||||
messageType: ['1']
|
||||
});
|
||||
await proxy?.$modal.confirm('是否确认提交?');
|
||||
loading.value = true;
|
||||
@@ -139,7 +140,8 @@ const addMultiInstanceUser = async (data) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userIds: data.map((e) => e.userId),
|
||||
taskId: task.value.id,
|
||||
message: ''
|
||||
message: '',
|
||||
messageType: ['1']
|
||||
});
|
||||
await proxy?.$modal.confirm('是否确认提交?');
|
||||
loading.value = true;
|
||||
@@ -163,7 +165,8 @@ const deleteMultiInstanceUser = async (row) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userIds: [row.userId],
|
||||
taskId: task.value.id,
|
||||
message: ''
|
||||
message: '',
|
||||
messageType: ['1']
|
||||
});
|
||||
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<el-checkbox value="3" name="type">短信</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="task.flowStatus === 'waiting'" label="附件">
|
||||
<el-form-item label="附件" v-if="buttonObj.file">
|
||||
<fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="抄送" v-if="task.flowStatus === 'waiting' && buttonObj.copy">
|
||||
<el-form-item label="抄送" v-if="buttonObj.copy">
|
||||
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
|
||||
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
|
||||
{{ user.nickName }}
|
||||
@@ -80,7 +80,13 @@
|
||||
<!-- 加签组件 -->
|
||||
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
|
||||
<!-- 弹窗选人 -->
|
||||
<UserSelect ref="porUserRef" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
|
||||
<UserSelect
|
||||
ref="porUserRef"
|
||||
:data="form.assigneeMap[nodeCode]"
|
||||
:multiple="true"
|
||||
:userIds="popUserIds"
|
||||
@confirm-call-back="handlePopUser"
|
||||
></UserSelect>
|
||||
|
||||
<!-- 驳回开始 -->
|
||||
<el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
|
||||
@@ -149,8 +155,7 @@ import {
|
||||
import UserSelect from '@/components/UserSelect';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
import { UserVO } from '@/api/system/user/types';
|
||||
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
|
||||
import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
|
||||
|
||||
const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
|
||||
const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
|
||||
@@ -171,9 +176,11 @@ const buttonDisabled = ref(true);
|
||||
//任务id
|
||||
const taskId = ref<string>('');
|
||||
//抄送人
|
||||
const selectCopyUserList = ref<UserVO[]>([]);
|
||||
const selectCopyUserList = ref<FlowCopyVo[]>([]);
|
||||
//抄送人id
|
||||
const selectCopyUserIds = ref<string>(undefined);
|
||||
//自定义节点变量
|
||||
const varNodeList = ref<Map<string, string>>(undefined);
|
||||
//可减签的人员
|
||||
const deleteUserList = ref<any>([]);
|
||||
//弹窗可选择的人员id
|
||||
@@ -217,7 +224,11 @@ const task = ref<FlowTaskVO>({
|
||||
nodeType: undefined,
|
||||
nodeRatio: undefined,
|
||||
applyNode: false,
|
||||
buttonList: []
|
||||
buttonList: [],
|
||||
copyList: [],
|
||||
varList: undefined,
|
||||
businessCode: undefined,
|
||||
businessTitle: undefined
|
||||
});
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
@@ -254,17 +265,24 @@ const openDialog = async (id?: string) => {
|
||||
const response = await getTask(taskId.value);
|
||||
task.value = response.data;
|
||||
buttonObj.value = {};
|
||||
task.value.buttonList.forEach((e) => {
|
||||
task.value.buttonList?.forEach((e) => {
|
||||
buttonObj.value[e.code] = e.show;
|
||||
});
|
||||
selectCopyUserList.value = task.value.copyList;
|
||||
selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
|
||||
varNodeList.value = task.value.varList;
|
||||
console.log('varNodeList', varNodeList.value);
|
||||
buttonDisabled.value = false;
|
||||
const data = {
|
||||
taskId: taskId.value,
|
||||
variables: props.taskVariables
|
||||
};
|
||||
const nextData = await getNextNodeList(data);
|
||||
nestNodeList.value = nextData.data;
|
||||
loading.value = false;
|
||||
try {
|
||||
const data = {
|
||||
taskId: taskId.value,
|
||||
variables: props.taskVariables
|
||||
};
|
||||
const nextData = await getNextNodeList(data);
|
||||
nestNodeList.value = nextData.data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {});
|
||||
@@ -273,7 +291,7 @@ const emits = defineEmits(['submitCallback', 'cancelCallback']);
|
||||
/** 办理流程 */
|
||||
const handleCompleteTask = async () => {
|
||||
form.value.taskId = taskId.value;
|
||||
form.value.taskVariables = props.taskVariables;
|
||||
form.value.variables = props.taskVariables;
|
||||
let verify = false;
|
||||
if (buttonObj.value.pop && nestNodeList.value && nestNodeList.value.length > 0) {
|
||||
nestNodeList.value.forEach((e) => {
|
||||
@@ -298,7 +316,7 @@ const handleCompleteTask = async () => {
|
||||
selectCopyUserList.value.forEach((e) => {
|
||||
const copyUser = {
|
||||
userId: e.userId,
|
||||
userName: e.nickName
|
||||
nickName: e.nickName
|
||||
};
|
||||
flowCopyList.push(copyUser);
|
||||
});
|
||||
@@ -325,7 +343,7 @@ const handleBackProcessOpen = async () => {
|
||||
backVisible.value = true;
|
||||
backLoading.value = true;
|
||||
backButtonDisabled.value = true;
|
||||
const data = await getBackTaskNode(task.value.definitionId, task.value.nodeCode);
|
||||
const data = await getBackTaskNode(task.value.id, task.value.nodeCode);
|
||||
taskNodeList.value = data.data;
|
||||
backLoading.value = false;
|
||||
backButtonDisabled.value = false;
|
||||
@@ -361,14 +379,14 @@ const openUserSelectCopy = () => {
|
||||
userSelectCopyRef.value.open();
|
||||
};
|
||||
//确认抄送人员
|
||||
const userSelectCopyCallBack = (data: UserVO[]) => {
|
||||
const userSelectCopyCallBack = (data: FlowCopyVo[]) => {
|
||||
if (data && data.length > 0) {
|
||||
selectCopyUserList.value = data;
|
||||
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
|
||||
}
|
||||
};
|
||||
//删除抄送人员
|
||||
const handleCopyCloseTag = (user: UserVO) => {
|
||||
const handleCopyCloseTag = (user: FlowCopyVo) => {
|
||||
const userId = user.userId;
|
||||
// 使用split删除用户
|
||||
const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);
|
||||
@@ -385,7 +403,8 @@ const addMultiInstanceUser = async (data) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userIds: data.map((e) => e.userId),
|
||||
taskId: taskId.value,
|
||||
message: form.value.message
|
||||
message: form.value.message,
|
||||
messageType: ['1']
|
||||
});
|
||||
await proxy?.$modal.confirm('是否确认提交?');
|
||||
loading.value = true;
|
||||
@@ -409,7 +428,8 @@ const deleteMultiInstanceUser = async (row) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userIds: [row.userId],
|
||||
taskId: taskId.value,
|
||||
message: form.value.message
|
||||
message: form.value.message,
|
||||
messageType: ['1']
|
||||
});
|
||||
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
||||
loading.value = false;
|
||||
@@ -429,7 +449,8 @@ const handleTransferTask = async (data) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userId: data[0].userId,
|
||||
taskId: taskId.value,
|
||||
message: form.value.message
|
||||
message: form.value.message,
|
||||
messageType: ['1']
|
||||
});
|
||||
await proxy?.$modal.confirm('是否确认提交?');
|
||||
loading.value = true;
|
||||
@@ -456,7 +477,8 @@ const handleDelegateTask = async (data) => {
|
||||
const taskOperationBo = reactive<TaskOperationBo>({
|
||||
userId: data[0].userId,
|
||||
taskId: taskId.value,
|
||||
message: form.value.message
|
||||
message: form.value.message,
|
||||
messageType: ['1']
|
||||
});
|
||||
await proxy?.$modal.confirm('是否确认提交?');
|
||||
loading.value = true;
|
||||
|
||||
@@ -47,11 +47,11 @@ const routers = computed(() => permissionStore.getTopbarRoutes());
|
||||
|
||||
// 顶部显示菜单
|
||||
const topMenus = computed(() => {
|
||||
let topMenus: RouteRecordRaw[] = [];
|
||||
const topMenus: RouteRecordRaw[] = [];
|
||||
routers.value.map((menu) => {
|
||||
if (menu.hidden !== true) {
|
||||
// 兼容顶部栏一级菜单内部跳转
|
||||
if (menu.path === '/') {
|
||||
if (menu.path === '/' && menu.children) {
|
||||
topMenus.push(menu.children ? menu.children[0] : menu);
|
||||
} else {
|
||||
topMenus.push(menu);
|
||||
@@ -63,7 +63,7 @@ const topMenus = computed(() => {
|
||||
|
||||
// 设置子路由
|
||||
const childrenMenus = computed(() => {
|
||||
let childrenMenus: RouteRecordRaw[] = [];
|
||||
const childrenMenus: RouteRecordRaw[] = [];
|
||||
routers.value.map((router) => {
|
||||
router.children?.forEach((item) => {
|
||||
if (item.parentPath === undefined) {
|
||||
@@ -118,7 +118,7 @@ const handleSelect = (key: string) => {
|
||||
// 没有子路由路径内部打开
|
||||
const routeMenu = childrenMenus.value.find((item) => item.path === key);
|
||||
if (routeMenu && routeMenu.query) {
|
||||
let query = JSON.parse(routeMenu.query);
|
||||
const query = JSON.parse(routeMenu.query);
|
||||
router.push({ path: key, query: query });
|
||||
} else {
|
||||
router.push({ path: key });
|
||||
@@ -132,7 +132,7 @@ const handleSelect = (key: string) => {
|
||||
};
|
||||
|
||||
const activeRoutes = (key: string) => {
|
||||
let routes: RouteRecordRaw[] = [];
|
||||
const routes: RouteRecordRaw[] = [];
|
||||
if (childrenMenus.value && childrenMenus.value.length > 0) {
|
||||
childrenMenus.value.map((item) => {
|
||||
if (key == item.parentPath || (key == 'index' && '' == item.path)) {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<div class="el-tree-select">
|
||||
<el-select
|
||||
ref="treeSelect"
|
||||
v-model="valueId"
|
||||
style="width: 100%"
|
||||
:filterable="true"
|
||||
:clearable="true"
|
||||
:filter-method="selectFilterData as any"
|
||||
:placeholder="placeholder"
|
||||
@clear="clearHandle"
|
||||
>
|
||||
<el-option :value="valueId" :label="valueTitle">
|
||||
<el-tree
|
||||
id="tree-option"
|
||||
ref="selectTree"
|
||||
:accordion="accordion"
|
||||
:data="options"
|
||||
:props="objMap as any"
|
||||
: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 lang="ts">
|
||||
interface ObjMap {
|
||||
value: string;
|
||||
label: string;
|
||||
children: string;
|
||||
}
|
||||
interface Props {
|
||||
objMap: ObjMap;
|
||||
accordion: boolean;
|
||||
value: string | number;
|
||||
options: any[];
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
objMap: () => {
|
||||
return {
|
||||
value: 'id',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
};
|
||||
},
|
||||
accordion: false,
|
||||
value: '',
|
||||
options: () => [],
|
||||
placeholder: ''
|
||||
});
|
||||
|
||||
const selectTree = ref<ElTreeSelectInstance>();
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
const valueId = computed({
|
||||
get: () => props.value,
|
||||
set: (val) => {
|
||||
emit('update:value', val);
|
||||
}
|
||||
});
|
||||
const valueTitle = ref('');
|
||||
const defaultExpandedKey = ref<any[]>([]);
|
||||
|
||||
const initHandle = () => {
|
||||
nextTick(() => {
|
||||
const selectedValue = valueId.value;
|
||||
if (selectedValue !== null && typeof selectedValue !== 'undefined') {
|
||||
const node = selectTree.value?.getNode(selectedValue);
|
||||
if (node) {
|
||||
valueTitle.value = node.data[props.objMap.label];
|
||||
selectTree.value?.setCurrentKey(selectedValue); // 设置默认选中
|
||||
defaultExpandedKey.value = [selectedValue]; // 设置默认展开
|
||||
}
|
||||
} else {
|
||||
clearHandle();
|
||||
}
|
||||
});
|
||||
};
|
||||
const handleNodeClick = (node: any) => {
|
||||
valueTitle.value = node[props.objMap.label];
|
||||
valueId.value = node[props.objMap.value];
|
||||
defaultExpandedKey.value = [];
|
||||
selectTree.value?.blur();
|
||||
selectFilterData('');
|
||||
};
|
||||
const selectFilterData = (val: any) => {
|
||||
selectTree.value?.filter(val);
|
||||
};
|
||||
const filterNode = (value: any, data: any) => {
|
||||
if (!value) return true;
|
||||
return data[props.objMap['label']].indexOf(value) !== -1;
|
||||
};
|
||||
const clearHandle = () => {
|
||||
valueTitle.value = '';
|
||||
valueId.value = '';
|
||||
defaultExpandedKey.value = [];
|
||||
clearSelected();
|
||||
};
|
||||
const 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>
|
||||
@@ -168,12 +168,15 @@ const confirm = () => {
|
||||
};
|
||||
|
||||
const computedIds = (data) => {
|
||||
if (data === '' || data === null || data === undefined) {
|
||||
return [];
|
||||
}
|
||||
if (data instanceof Array) {
|
||||
return data.map((item) => String(item));
|
||||
} else if (typeof data === 'string') {
|
||||
return data.split(',');
|
||||
} else if (typeof data === 'number') {
|
||||
return [data];
|
||||
return [String(data)];
|
||||
} else {
|
||||
console.warn('<UserSelect> The data type of data should be array or string or number, but I received other');
|
||||
return [];
|
||||
|
||||
17
src/enums/NavTypeEnum.ts
Normal file
17
src/enums/NavTypeEnum.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 导航栏布局枚举
|
||||
*/
|
||||
export enum NavTypeEnum {
|
||||
/**
|
||||
* 左侧导航
|
||||
*/
|
||||
LEFT = 'left',
|
||||
/**
|
||||
* 顶部导航
|
||||
*/
|
||||
TOP = 'top',
|
||||
/**
|
||||
* 混合导航
|
||||
*/
|
||||
MIX = 'mix'
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition v-if="!route.meta.noCache" :enter-active-class="animate" mode="out-in">
|
||||
<keep-alive v-if="!route.meta.noCache" :include="tagsViewStore.cachedViews">
|
||||
<transition :enter-active-class="animate" mode="out-in">
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
<component :is="Component" v-if="!route.meta.link" :key="route.path" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<transition v-if="route.meta.noCache" :enter-active-class="animate" mode="out-in">
|
||||
<component :is="Component" v-if="!route.meta.link && route.meta.noCache" :key="route.path" />
|
||||
</transition>
|
||||
</router-view>
|
||||
<iframe-toggle />
|
||||
</section>
|
||||
@@ -40,16 +37,16 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
addIframe()
|
||||
})
|
||||
addIframe();
|
||||
});
|
||||
|
||||
watchEffect((route) => {
|
||||
addIframe()
|
||||
})
|
||||
watchEffect(() => {
|
||||
addIframe();
|
||||
});
|
||||
|
||||
function addIframe() {
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().addIframeView(route)
|
||||
useTagsViewStore().addIframeView(route);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -92,11 +89,11 @@ function addIframe() {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c0c0c0;
|
||||
border-radius: 3px;
|
||||
background-color: var(--el-text-color-placeholder);
|
||||
border-radius: 999px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,7 @@ const tagsViewStore = useTagsViewStore();
|
||||
|
||||
function iframeUrl(url: string | undefined, query: any) {
|
||||
if (Object.keys(query).length > 0) {
|
||||
let params = Object.keys(query)
|
||||
const params = Object.keys(query)
|
||||
.map((key) => key + '=' + query[key])
|
||||
.join('&');
|
||||
return url + '?' + params;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<div class="navbar" :class="'nav' + navType">
|
||||
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
|
||||
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
|
||||
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
|
||||
|
||||
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
|
||||
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
|
||||
|
||||
<template v-if="navType == NavTypeEnum.TOP">
|
||||
<logo v-show="showLogo" :collapse="false"></logo>
|
||||
<top-bar id="topbar-container" class="topbar-container" />
|
||||
</template>
|
||||
<div class="right-menu flex align-center">
|
||||
<template v-if="appStore.device !== 'mobile'">
|
||||
<el-select
|
||||
@@ -33,7 +38,7 @@
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
|
||||
<svg-icon icon-class="message" />
|
||||
<div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
|
||||
</el-badge>
|
||||
</template>
|
||||
<template #default>
|
||||
@@ -98,7 +103,10 @@ import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
|
||||
import { TenantVO } from '@/api/types';
|
||||
import notice from './notice/index.vue';
|
||||
import router from '@/router';
|
||||
import {ElMessageBoxOptions} from "element-plus/es/components/message-box/src/message-box.type";
|
||||
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
import Logo from "@/layout/components/Sidebar/Logo.vue";
|
||||
import TopBar from './TopBar'
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
@@ -109,6 +117,9 @@ const newNotice = ref(<number>0);
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const userId = ref(userStore.userId);
|
||||
const navType = computed(() => settingsStore.navType);
|
||||
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||
|
||||
const companyName = ref(undefined);
|
||||
const tenantList = ref<TenantVO[]>([]);
|
||||
// 是否切换了租户
|
||||
@@ -171,6 +182,7 @@ const logout = async () => {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
|
||||
}
|
||||
});
|
||||
proxy?.$tab.closeAllPage();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -200,6 +212,12 @@ watch(
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar.navtop {
|
||||
.hamburger-container {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-select .el-input__wrapper) {
|
||||
height: 30px;
|
||||
}
|
||||
@@ -220,24 +238,34 @@ watch(
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
//background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
background: var(--el-bg-color);
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
//float: left;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
//float: left;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.topmenu-container {
|
||||
@@ -245,16 +273,28 @@ watch(
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.topbar-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
.errLog-container {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
float: right;
|
||||
//float: right;
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
@@ -265,7 +305,7 @@ watch(
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #5a5e66;
|
||||
color: var(--el-text-color-regular);
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.hover-effect {
|
||||
@@ -273,7 +313,7 @@ watch(
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +329,7 @@ watch(
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
border-radius: var(--app-radius-md);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
<template>
|
||||
<el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
|
||||
<h3 class="drawer-title">菜单导航设置</h3>
|
||||
<div class="nav-wrap">
|
||||
<el-tooltip content="左侧菜单" placement="bottom">
|
||||
<div
|
||||
class="item left"
|
||||
@click="handleNavType(NavTypeEnum.LEFT)"
|
||||
:style="{ '--theme': theme }"
|
||||
:class="{ activeItem: navType == NavTypeEnum.LEFT }"
|
||||
>
|
||||
<b></b><b></b>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="混合菜单" placement="bottom">
|
||||
<div
|
||||
class="item mix"
|
||||
@click="handleNavType(NavTypeEnum.MIX)"
|
||||
:style="{ '--theme': theme }"
|
||||
:class="{ activeItem: navType == NavTypeEnum.MIX }"
|
||||
>
|
||||
<b></b><b></b>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="顶部菜单" placement="bottom">
|
||||
<div
|
||||
class="item top"
|
||||
@click="handleNavType(NavTypeEnum.TOP)"
|
||||
:style="{ '--theme': theme }"
|
||||
:class="{ activeItem: navType == NavTypeEnum.TOP }"
|
||||
>
|
||||
<b></b><b></b>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<h3 class="drawer-title">主题风格设置</h3>
|
||||
|
||||
<div class="setting-drawer-block-checbox">
|
||||
@@ -46,16 +81,16 @@
|
||||
<h3 class="drawer-title">系统布局配置</h3>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 TopNav</span>
|
||||
<span>开启 Tags-Views</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.topNav" class="drawer-switch" @change="topNavChange" />
|
||||
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 Tags-Views</span>
|
||||
<span>显示页签图标</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
||||
<el-switch v-model="settingsStore.tagsIcon" :disabled="!settingsStore.tagsView" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +129,7 @@ import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { usePermissionStore } from '@/store/modules/permission';
|
||||
import { handleThemeStyle } from '@/utils/theme';
|
||||
import { SideThemeEnum } from '@/enums/SideThemeEnum';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
import defaultSettings from '@/settings';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -106,7 +142,7 @@ 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 navType = ref(settingsStore.navType);
|
||||
// 是否暗黑模式
|
||||
const isDark = useDark({
|
||||
storageKey: 'useDarkKey',
|
||||
@@ -123,11 +159,26 @@ watch(isDark, () => {
|
||||
});
|
||||
const toggleDark = () => useToggle(isDark);
|
||||
|
||||
const topNavChange = (val: any) => {
|
||||
if (!val) {
|
||||
appStore.toggleSideBarHide(false);
|
||||
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
||||
}
|
||||
/** 菜单导航设置 */
|
||||
watch(
|
||||
() => navType,
|
||||
(val: string) => {
|
||||
if (val.value === NavTypeEnum.TOP) {
|
||||
appStore.toggleSideBarHide(true);
|
||||
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
||||
} else if (val.value === NavTypeEnum.LEFT) {
|
||||
appStore.toggleSideBarHide(false);
|
||||
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
||||
} else if (val.value === NavTypeEnum.MIX) {
|
||||
appStore.toggleSideBarHide(false);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const handleNavType = (val: NavTypeEnum) => {
|
||||
settingsStore.navType = val;
|
||||
navType.value = val;
|
||||
};
|
||||
|
||||
const dynamicTitleChange = () => {
|
||||
@@ -151,13 +202,14 @@ const handleTheme = (val: string) => {
|
||||
const saveSetting = () => {
|
||||
proxy?.$modal.loading('正在保存到本地,请稍候...');
|
||||
const settings = useStorage<LayoutSetting>('layout-setting', defaultSettings);
|
||||
settings.value.topNav = storeSettings.value.topNav;
|
||||
settings.value.tagsView = storeSettings.value.tagsView;
|
||||
settings.value.tagsIcon = storeSettings.value.tagsIcon;
|
||||
settings.value.fixedHeader = storeSettings.value.fixedHeader;
|
||||
settings.value.sidebarLogo = storeSettings.value.sidebarLogo;
|
||||
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
|
||||
settings.value.sideTheme = storeSettings.value.sideTheme;
|
||||
settings.value.theme = storeSettings.value.theme;
|
||||
settings.value.navType = storeSettings.value.navType;
|
||||
setTimeout(() => {
|
||||
proxy?.$modal.closeLoading();
|
||||
}, 1000);
|
||||
@@ -235,4 +287,67 @@ defineExpose({
|
||||
margin: -3px 8px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
// 导航模式
|
||||
.nav-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.activeItem {
|
||||
border: 2px solid #{'var(--theme)'} !important;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
margin-right: 16px;
|
||||
cursor: pointer;
|
||||
width: 56px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
background: #f0f2f5;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.left {
|
||||
b:first-child {
|
||||
display: block;
|
||||
height: 30%;
|
||||
background: #fff;
|
||||
}
|
||||
b:last-child {
|
||||
width: 30%;
|
||||
background: #1b2a47;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
}
|
||||
.mix {
|
||||
b:first-child {
|
||||
border-radius: 4px 4px 0 0;
|
||||
display: block;
|
||||
height: 30%;
|
||||
background: #1b2a47;
|
||||
}
|
||||
b:last-child {
|
||||
width: 30%;
|
||||
background: #1b2a47;
|
||||
position: absolute;
|
||||
height: 70%;
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
}
|
||||
.top {
|
||||
b:first-child {
|
||||
display: block;
|
||||
height: 30%;
|
||||
background: #1b2a47;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
<div
|
||||
class="sidebar-logo-container"
|
||||
:class="{ collapse: collapse }"
|
||||
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
|
||||
>
|
||||
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
|
||||
<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 }">
|
||||
<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 }">
|
||||
<h1 class="sidebar-title">
|
||||
{{ title }}
|
||||
</h1>
|
||||
</router-link>
|
||||
@@ -26,6 +25,7 @@ import variables from '@/assets/styles/variables.module.scss';
|
||||
import logo from '@/assets/logo/logo.png';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
|
||||
defineProps({
|
||||
collapse: {
|
||||
@@ -34,9 +34,31 @@ defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const title = ref('RuoYi-Vue-Plus');
|
||||
const title = import.meta.env.VITE_APP_LOGO_TITLE;
|
||||
const settingsStore = useSettingsStore();
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
|
||||
// 获取Logo背景色
|
||||
const getLogoBackground = computed(() => {
|
||||
if (settingsStore.isDark) {
|
||||
return 'var(--sidebar-bg)'
|
||||
}
|
||||
if (settingsStore.navType == NavTypeEnum.TOP) {
|
||||
return variables.menuLightBackground
|
||||
}
|
||||
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBackground
|
||||
})
|
||||
|
||||
// 获取Logo文字颜色
|
||||
const getLogoTextColor = computed(() => {
|
||||
if (settingsStore.isDark) {
|
||||
return 'var(--sidebar-text)'
|
||||
}
|
||||
if (settingsStore.navType == NavTypeEnum.TOP) {
|
||||
return variables.logoLightTitleColor
|
||||
}
|
||||
return sideTheme.value === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -51,10 +73,9 @@ const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
background: #2b2f3a;
|
||||
background: v-bind(getLogoBackground);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -67,21 +88,17 @@ const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
& .sidebar-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
color: v-bind(getLogoTextColor);
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
font-size: 14px;
|
||||
font-family:
|
||||
Avenir,
|
||||
Helvetica Neue,
|
||||
Arial,
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ const resolvePath = (routePath: string, routeQuery?: string): any => {
|
||||
return props.basePath;
|
||||
}
|
||||
if (routeQuery) {
|
||||
let query = JSON.parse(routeQuery);
|
||||
const query = JSON.parse(routeQuery);
|
||||
return { path: getNormalPath(props.basePath + '/' + routePath), query: query };
|
||||
}
|
||||
return getNormalPath(props.basePath + '/' + routePath);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
:unique-opened="true"
|
||||
:active-text-color="theme"
|
||||
:collapse-transition="false"
|
||||
:popper-offset="12"
|
||||
mode="vertical"
|
||||
>
|
||||
<sidebar-item v-for="(r, index) in sidebarRouters" :key="r.path + index" :item="r" :base-path="r.path" />
|
||||
|
||||
@@ -63,9 +63,9 @@ const loginByCode = async (data: LoginData) => {
|
||||
|
||||
const init = async () => {
|
||||
// 如果域名不相等 则重定向处理
|
||||
let host = window.location.host;
|
||||
const host = window.location.host;
|
||||
if (domain !== host) {
|
||||
let urlFull = new URL(window.location.href);
|
||||
const urlFull = new URL(window.location.href);
|
||||
urlFull.host = domain;
|
||||
window.location.href = urlFull.toString();
|
||||
return;
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
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 ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
|
||||
<span class="tags-view-item-title">{{ tag.title }}</span>
|
||||
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
|
||||
</span>
|
||||
@@ -51,6 +52,7 @@ const router = useRouter();
|
||||
const visitedViews = computed(() => useTagsViewStore().getVisitedViews());
|
||||
const routes = computed(() => usePermissionStore().getRoutes());
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
const tagsIcon = computed(() => useSettingsStore().tagsIcon)
|
||||
|
||||
watch(route, () => {
|
||||
addTags();
|
||||
@@ -242,16 +244,15 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.12),
|
||||
0 0 3px 0 rgba(0, 0, 0, 0.04);
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
line-height: 23px;
|
||||
line-height: 25px;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
color: #495060;
|
||||
@@ -259,8 +260,13 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
border-radius: var(--app-radius-md);
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
@@ -285,6 +291,13 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.tags-view-item.active.has-icon::before {
|
||||
content: none !important;
|
||||
}
|
||||
.tags-view-item-title {
|
||||
margin-left: 4px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: var(--el-bg-color);
|
||||
@@ -292,16 +305,16 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--app-radius-md);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
src/layout/components/TopBar/index.vue
Normal file
103
src/layout/components/TopBar/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
|
||||
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
|
||||
|
||||
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" v-if="moreRoutes.length > 0">
|
||||
<template #title>
|
||||
<span>更多菜单</span>
|
||||
</template>
|
||||
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SidebarItem from '../Sidebar/SidebarItem'
|
||||
import {useAppStore} from '@/store/modules/app'
|
||||
import {useSettingsStore} from '@/store/modules/settings'
|
||||
import {usePermissionStore} from '@/store/modules/permission'
|
||||
|
||||
const route = useRoute()
|
||||
const appStore = useAppStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
|
||||
const theme = computed(() => settingsStore.theme)
|
||||
const device = computed(() => appStore.device)
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
})
|
||||
|
||||
const visibleNumber = ref(5)
|
||||
const topMenus = computed(() => {
|
||||
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
|
||||
})
|
||||
const moreRoutes = computed(() => {
|
||||
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value)
|
||||
})
|
||||
function setVisibleNumber() {
|
||||
let width = document.body.getBoundingClientRect().width
|
||||
if (width >= 1000) {
|
||||
width -= 500
|
||||
}
|
||||
visibleNumber.value = parseInt(width / 3 / 85)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', setVisibleNumber)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', setVisibleNumber)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setVisibleNumber()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
/* menu item */
|
||||
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
.topbar-menu.el-menu--horizontal > .el-menu-item {
|
||||
float: left;
|
||||
height: 50px !important;
|
||||
line-height: 50px !important;
|
||||
color: #303133 !important;
|
||||
padding: 0 5px !important;
|
||||
margin: 0 10px !important;
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span {
|
||||
color: v-bind(theme);
|
||||
}
|
||||
|
||||
/* sub-menu item */
|
||||
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
|
||||
float: left;
|
||||
line-height: 50px !important;
|
||||
color: #303133 !important;
|
||||
margin: 0 15px -3px!important;
|
||||
}
|
||||
|
||||
/* topbar more arrow */
|
||||
.topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
|
||||
position: static;
|
||||
margin-left: 8px;
|
||||
margin-top: 0px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* menu__title el-menu-item */
|
||||
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
|
||||
height: 60px;
|
||||
}
|
||||
</style>
|
||||
@@ -67,7 +67,7 @@ const closeSearch = () => {
|
||||
};
|
||||
// 菜单搜索数据过滤
|
||||
const menuSearch = (queryString: string, cb: (options: any[]) => void) => {
|
||||
let options = state.menuList.filter((item) => {
|
||||
const options = state.menuList.filter((item) => {
|
||||
return item.title.indexOf(queryString) > -1;
|
||||
});
|
||||
cb(options);
|
||||
|
||||
@@ -24,10 +24,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNoticeStore } from '@/store/modules/notice';
|
||||
|
||||
const noticeStore = storeToRefs(useNoticeStore());
|
||||
const noticeStore = useNoticeStore();
|
||||
const { readAll } = useNoticeStore();
|
||||
|
||||
// 定义变量内容
|
||||
@@ -42,7 +41,7 @@ const newsList = ref([]) as any;
|
||||
*/
|
||||
const getTableData = async () => {
|
||||
state.loading = true;
|
||||
newsList.value = noticeStore.state.value.notices;
|
||||
newsList.value = noticeStore.state.notices;
|
||||
state.loading = false;
|
||||
};
|
||||
|
||||
@@ -50,7 +49,7 @@ const getTableData = async () => {
|
||||
const onNewsClick = (item: any) => {
|
||||
newsList.value[item].read = true;
|
||||
//并且写入pinia
|
||||
noticeStore.state.value.notices = newsList.value;
|
||||
noticeStore.state.notices = newsList.value;
|
||||
};
|
||||
|
||||
// 前往通知中心点击
|
||||
|
||||
@@ -26,6 +26,7 @@ import SideBar from './components/Sidebar/index.vue';
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
import { initWebSocket } from '@/utils/websocket';
|
||||
import { initSSE } from '@/utils/sse';
|
||||
|
||||
@@ -35,6 +36,13 @@ const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
const layout = computed(() => settingsStore.navType);
|
||||
|
||||
// 根据布局模式判断是否显示侧边栏
|
||||
const showSidebar = computed(() => {
|
||||
if (sidebar.value.hide) return false;
|
||||
return layout.value === NavTypeEnum.LEFT || layout.value === NavTypeEnum.MIX;
|
||||
});
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createApp } from 'vue';
|
||||
// global css
|
||||
import 'virtual:uno.css';
|
||||
import '@/assets/styles/index.scss';
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
import '@/assets/styles/index.scss';
|
||||
|
||||
// App、router、store
|
||||
import App from './App.vue';
|
||||
@@ -34,7 +34,7 @@ import i18n from '@/lang/index';
|
||||
// vxeTable
|
||||
import VXETable from 'vxe-table';
|
||||
import 'vxe-table/lib/style.css';
|
||||
VXETable.config({
|
||||
VXETable.setConfig({
|
||||
zIndex: 999999
|
||||
});
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ NProgress.configure({ showSpinner: false });
|
||||
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
|
||||
|
||||
const isWhiteList = (path: string) => {
|
||||
return whiteList.some(pattern => isPathMatch(pattern, path))
|
||||
}
|
||||
return whiteList.some((pattern) => isPathMatch(pattern, path));
|
||||
};
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
|
||||
@@ -93,104 +93,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
|
||||
// 动态路由,基于用户权限动态去加载
|
||||
export const dynamicRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/system/user-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:user:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'role/:userId(\\d+)',
|
||||
component: () => import('@/views/system/user/authRole.vue'),
|
||||
name: 'AuthRole',
|
||||
meta: { title: '分配角色', activeMenu: '/system/user', icon: '', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/role-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:role:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'user/:roleId(\\d+)',
|
||||
component: () => import('@/views/system/role/authUser.vue'),
|
||||
name: 'AuthUser',
|
||||
meta: { title: '分配用户', activeMenu: '/system/role', icon: '', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/dict-data',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:dict:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:dictId(\\d+)',
|
||||
component: () => import('@/views/system/dict/data.vue'),
|
||||
name: 'Data',
|
||||
meta: { title: '字典数据', activeMenu: '/system/dict', icon: '', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/oss-config',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:ossConfig:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/system/oss/config.vue'),
|
||||
name: 'OssConfig',
|
||||
meta: { title: '配置管理', activeMenu: '/system/oss', icon: '', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/tool/gen-edit',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['tool:gen:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:tableId(\\d+)',
|
||||
component: () => import('@/views/tool/gen/editTable.vue'),
|
||||
name: 'GenEdit',
|
||||
meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/workflow/leaveEdit',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['workflow:leave:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/workflow/leave/leaveEdit.vue'),
|
||||
name: 'leaveEdit',
|
||||
meta: { title: '请假申请', activeMenu: '/workflow/leave', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/workflow/design',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['workflow:leave:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/workflow/processDefinition/design.vue'),
|
||||
name: 'design',
|
||||
meta: { title: '流程设计', activeMenu: '/workflow/processDefinition', noCache: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LanguageEnum } from '@/enums/LanguageEnum';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
|
||||
const setting: DefaultSettings = {
|
||||
/**
|
||||
@@ -18,19 +19,24 @@ const setting: DefaultSettings = {
|
||||
showSettings: true,
|
||||
|
||||
/**
|
||||
* 是否显示顶部导航
|
||||
* 默认布局
|
||||
*/
|
||||
topNav: false,
|
||||
navType: NavTypeEnum.LEFT,
|
||||
|
||||
/**
|
||||
* 是否显示 tagsView
|
||||
*/
|
||||
tagsView: true,
|
||||
|
||||
/**
|
||||
* 显示页签图标
|
||||
*/
|
||||
tagsIcon: false,
|
||||
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
fixedHeader: false,
|
||||
fixedHeader: true,
|
||||
|
||||
/**
|
||||
* 是否显示logo
|
||||
@@ -42,14 +48,6 @@ const setting: DefaultSettings = {
|
||||
*/
|
||||
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',
|
||||
|
||||
/**
|
||||
* 是否开启动画 开启随机 关闭渐进渐出
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createPinia } from "pinia";
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const store = createPinia();
|
||||
|
||||
|
||||
@@ -99,14 +99,14 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
};
|
||||
const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
|
||||
let children: RouteRecordRaw[] = [];
|
||||
childrenMap.forEach(el => {
|
||||
childrenMap.forEach((el) => {
|
||||
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
|
||||
if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
|
||||
children = children.concat(filterChildren(el.children, el));
|
||||
} else {
|
||||
children.push(el);
|
||||
}
|
||||
})
|
||||
});
|
||||
return children;
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -3,29 +3,32 @@ import defaultSettings from '@/settings';
|
||||
import { useDynamicTitle } from '@/utils/dynamicTitle';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { ref } from 'vue';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
|
||||
export const useSettingsStore = defineStore('setting', () => {
|
||||
// @ts-ignore
|
||||
const storageSetting = useStorage<LayoutSetting>('layout-setting', {
|
||||
topNav: defaultSettings.topNav,
|
||||
tagsView: defaultSettings.tagsView,
|
||||
tagsIcon: defaultSettings.tagsIcon,
|
||||
fixedHeader: defaultSettings.fixedHeader,
|
||||
sidebarLogo: defaultSettings.sidebarLogo,
|
||||
dynamicTitle: defaultSettings.dynamicTitle,
|
||||
sideTheme: defaultSettings.sideTheme,
|
||||
theme: defaultSettings.theme
|
||||
theme: defaultSettings.theme,
|
||||
navType: defaultSettings.navType
|
||||
});
|
||||
const title = ref<string>(defaultSettings.title);
|
||||
const theme = ref<string>(storageSetting.value.theme);
|
||||
const sideTheme = ref<string>(storageSetting.value.sideTheme);
|
||||
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
||||
const topNav = ref<boolean>(storageSetting.value.topNav);
|
||||
const tagsView = ref<boolean>(storageSetting.value.tagsView);
|
||||
const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
|
||||
const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
|
||||
const sidebarLogo = ref<boolean>(storageSetting.value.sidebarLogo);
|
||||
const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
|
||||
const animationEnable = ref<boolean>(defaultSettings.animationEnable);
|
||||
const dark = ref<boolean>(defaultSettings.dark);
|
||||
const navType = ref<NavTypeEnum>(storageSetting.value.navType || NavTypeEnum.LEFT);
|
||||
|
||||
const setTitle = (value: string) => {
|
||||
title.value = value;
|
||||
@@ -36,13 +39,14 @@ export const useSettingsStore = defineStore('setting', () => {
|
||||
theme,
|
||||
sideTheme,
|
||||
showSettings,
|
||||
topNav,
|
||||
tagsView,
|
||||
tagsIcon,
|
||||
fixedHeader,
|
||||
sidebarLogo,
|
||||
dynamicTitle,
|
||||
animationEnable,
|
||||
dark,
|
||||
navType,
|
||||
setTitle
|
||||
};
|
||||
});
|
||||
|
||||
11
src/types/global.d.ts
vendored
11
src/types/global.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
|
||||
import { LanguageEnum } from '@/enums/LanguageEnum';
|
||||
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||
|
||||
declare global {
|
||||
/** vue Instance */
|
||||
@@ -90,14 +91,18 @@ declare global {
|
||||
}
|
||||
declare interface LayoutSetting {
|
||||
/**
|
||||
* 是否显示顶部导航
|
||||
* 默认布局
|
||||
*/
|
||||
topNav: boolean;
|
||||
navType: NavTypeEnum;
|
||||
|
||||
/**
|
||||
* 是否显示多标签导航
|
||||
*/
|
||||
tagsView: boolean;
|
||||
/**
|
||||
* 显示页签图标
|
||||
*/
|
||||
tagsIcon: boolean;
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
@@ -157,8 +162,6 @@ declare global {
|
||||
* false: 明亮模式
|
||||
*/
|
||||
dark: boolean;
|
||||
|
||||
errorLog: string;
|
||||
}
|
||||
}
|
||||
export {};
|
||||
|
||||
3
src/types/module.d.ts
vendored
3
src/types/module.d.ts
vendored
@@ -12,7 +12,7 @@ import type { LanguageType } from '@/lang';
|
||||
|
||||
export {};
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
// 全局方法声明
|
||||
$modal: typeof modal;
|
||||
@@ -48,4 +48,3 @@ export type ObjKeysToUnion<T, P extends string = ''> = T extends object
|
||||
[K in keyof T]: ObjKeysToUnion<T[K], P extends '' ? `${K & string}` : `${P}.${K & string}`>;
|
||||
}[keyof T]
|
||||
: P;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min.js';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
// 密钥对生成 http://web.chacuo.net/netrsakeypair
|
||||
|
||||
const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;
|
||||
|
||||
@@ -28,7 +28,11 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 50000
|
||||
timeout: 50000,
|
||||
transitional: {
|
||||
// 超时错误更明确
|
||||
clarifyTimeoutError: true
|
||||
}
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
@@ -191,7 +195,8 @@ export function download(url: string, params: any, fileName: string) {
|
||||
const blob = new Blob([resp]);
|
||||
FileSaver.saveAs(blob, fileName);
|
||||
} else {
|
||||
const resText = await resp.data.text();
|
||||
const blob = new Blob([resp]);
|
||||
const resText = await blob.text();
|
||||
const rspObj = JSON.parse(resText);
|
||||
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
|
||||
ElMessage.error(errMsg);
|
||||
|
||||
@@ -178,11 +178,11 @@ export const handleTree = <T>(data: any[], id?: string, parentId?: string, child
|
||||
|
||||
for (const d of data) {
|
||||
const parentId = d[config.parentId];
|
||||
const parentObj = childrenListMap[parentId]
|
||||
const parentObj = childrenListMap[parentId];
|
||||
if (!parentObj) {
|
||||
tree.push(d);
|
||||
} else {
|
||||
parentObj[config.childrenList].push(d)
|
||||
parentObj[config.childrenList].push(d);
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
|
||||
@@ -11,10 +11,10 @@ export const initSSE = (url: any) => {
|
||||
url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
|
||||
const { data, error } = useEventSource(url, [], {
|
||||
autoReconnect: {
|
||||
retries: 10,
|
||||
delay: 3000,
|
||||
retries: 5,
|
||||
delay: 5000,
|
||||
onFailed() {
|
||||
console.log('Failed to connect after 10 retries');
|
||||
console.log('Failed to connect after 5 retries');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,15 +4,6 @@
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="部门id" prop="deptId">
|
||||
<el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户id" prop="userId">
|
||||
<el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序号" prop="orderNum">
|
||||
<el-input v-model="queryParams.orderNum" placeholder="请输入排序号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="key键" prop="testKey">
|
||||
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
@@ -49,7 +40,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="true" label="主键" align="center" prop="id" />
|
||||
<el-table-column label="部门id" align="center" prop="deptId" />
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
v-loading="loading"
|
||||
:data="treeList"
|
||||
row-key="id"
|
||||
border
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<script setup lang="ts">
|
||||
import errImage from '@/assets/401_images/401.gif';
|
||||
|
||||
let { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const errGif = ref(errImage + '?' + +new Date());
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
let message = computed(() => {
|
||||
const message = computed(() => {
|
||||
return '找不到网页!';
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
* 文件存储 七牛、阿里、腾讯 云存储<br />
|
||||
* 监控框架 SpringBoot-Admin 全方位服务监控<br />
|
||||
* 校验框架 Validation 增强接口安全性 严谨性<br />
|
||||
* Excel框架 Alibaba EasyExcel 性能优异 扩展性强<br />
|
||||
* Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
|
||||
* 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
|
||||
* 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
|
||||
* 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
|
||||
* 部署方式 Docker 容器编排 一键部署业务集群<br />
|
||||
* 国际化 SpringMessage Spring标准国际化方案<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v5.3.1</span></p>
|
||||
<p><b>当前版本:</b> <span>v5.6.0</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
@@ -77,7 +77,7 @@
|
||||
* 分布式监控 Prometheus、Grafana 全方位性能监控<br />
|
||||
* 其余与 Vue 版本一致<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v2.3.0</span></p>
|
||||
<p><b>当前版本:</b> <span>v2.6.0</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
|
||||
@@ -73,14 +73,14 @@
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
|
||||
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCodeImg, getTenantList } from '@/api/login';
|
||||
import { authBinding } from '@/api/system/social/auth';
|
||||
import { authRouterUrl } from '@/api/system/social/auth';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { LoginData, TenantVO } from '@/api/types';
|
||||
import { to } from 'await-to-js';
|
||||
@@ -176,6 +176,8 @@ const getCode = async () => {
|
||||
const { data } = res;
|
||||
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
|
||||
if (captchaEnabled.value) {
|
||||
// 刷新验证码时清空输入框
|
||||
loginForm.value.code = '';
|
||||
codeUrl.value = 'data:image/gif;base64,' + data.img;
|
||||
loginForm.value.uuid = data.uuid;
|
||||
}
|
||||
@@ -213,7 +215,7 @@ const initTenantList = async () => {
|
||||
* @param type
|
||||
*/
|
||||
const doSocialLogin = (type: string) => {
|
||||
authBinding(type, loginForm.value.tenantId).then((res: any) => {
|
||||
authRouterUrl(type, loginForm.value.tenantId).then((res: any) => {
|
||||
if (res.code === HttpStatus.SUCCESS) {
|
||||
// 获取授权地址跳转
|
||||
window.location.href = res.data;
|
||||
@@ -238,32 +240,40 @@ onMounted(() => {
|
||||
height: 100%;
|
||||
background-image: url('../assets/images/login-background.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.title-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
margin: 0px auto 26px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
:deep(.lang-select--style) {
|
||||
line-height: 0;
|
||||
color: #7483a3;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
|
||||
border-radius: var(--app-radius-lg);
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
width: min(420px, 90vw);
|
||||
padding: 32px 30px 12px 30px;
|
||||
z-index: 1;
|
||||
box-shadow: var(--app-shadow-lg);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
.el-input {
|
||||
height: 40px;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
@@ -282,14 +292,48 @@ onMounted(() => {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.login-form :deep(.el-input__wrapper) {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-input__wrapper.is-focus) {
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-button--primary) {
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-button.is-circle) {
|
||||
background: rgba(15, 23, 42, 0.04);
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-button.is-circle:hover) {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-color: rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 33%;
|
||||
width: calc(37% - 10px);
|
||||
height: 40px;
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--app-radius-sm);
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +344,7 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-family: Arial, serif;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
@@ -308,6 +352,27 @@ onMounted(() => {
|
||||
|
||||
.login-code-img {
|
||||
height: 40px;
|
||||
padding-left: 12px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
:global(html.dark) {
|
||||
.login-form {
|
||||
background: rgba(17, 24, 39, 0.9);
|
||||
border-color: rgba(148, 163, 184, 0.2);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-input__wrapper) {
|
||||
background-color: rgba(17, 24, 39, 0.7);
|
||||
}
|
||||
|
||||
.login-form :deep(.el-button.is-circle) {
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
border-color: rgba(148, 163, 184, 0.25);
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.el-login-footer {
|
||||
color: rgba(226, 232, 240, 0.65);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
v-loading="loading"
|
||||
:data="loginInfoList"
|
||||
:default-sort="defaultSort"
|
||||
border
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<el-card shadow="hover">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
|
||||
style="width: 100%"
|
||||
>
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
ref="operLogTableRef"
|
||||
v-loading="loading"
|
||||
:data="operlogList"
|
||||
border
|
||||
:default-sort="defaultSort"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="handleSortChange"
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-register-footer">
|
||||
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
|
||||
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -203,7 +203,6 @@ onMounted(() => {
|
||||
line-height: 0;
|
||||
color: #7483a3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.register-form {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="clientList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" :data="clientList" border @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="true" label="id" align="center" prop="id" />
|
||||
<el-table-column label="客户端id" align="center" prop="clientId" />
|
||||
@@ -300,7 +300,7 @@ const handleExport = () => {
|
||||
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: ClientVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '"吗?');
|
||||
await changeStatus(row.clientId, row.status);
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="configList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
|
||||
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
v-loading="loading"
|
||||
:data="deptList"
|
||||
row-key="deptId"
|
||||
border
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
>
|
||||
@@ -196,7 +197,7 @@ const initData: PageData<DeptForm, DeptQuery> = {
|
||||
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
|
||||
orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
|
||||
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
|
||||
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
|
||||
phone: [{ pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
|
||||
}
|
||||
};
|
||||
const data = reactive<PageData<DeptForm, DeptQuery>>(initData);
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="字典名称" prop="dictType">
|
||||
<el-select v-model="queryParams.dictType">
|
||||
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="字典标签" prop="dictLabel">
|
||||
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
|
||||
删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
|
||||
<el-table-column label="字典标签" align="center" prop="dictLabel">
|
||||
<template #default="scope">
|
||||
<span
|
||||
v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
|
||||
>{{ scope.row.dictLabel }}</span
|
||||
>
|
||||
<el-tag
|
||||
v-else
|
||||
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
|
||||
:class="scope.row.cssClass"
|
||||
>{{ scope.row.dictLabel }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典键值" align="center" prop="dictValue" />
|
||||
<el-table-column label="字典排序" align="center" prop="dictSort" />
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改参数配置对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="字典类型">
|
||||
<el-input v-model="form.dictType" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据标签" prop="dictLabel">
|
||||
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据键值" prop="dictValue">
|
||||
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="样式属性" prop="cssClass">
|
||||
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示排序" prop="dictSort">
|
||||
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回显样式" prop="listClass">
|
||||
<el-select v-model="form.listClass">
|
||||
<el-option
|
||||
v-for="item in listClassOptions"
|
||||
:key="item.value"
|
||||
:label="item.label + '(' + item.value + ')'"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Data" lang="ts">
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
|
||||
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
|
||||
import { DictTypeVO } from '@/api/system/dict/type/types';
|
||||
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
|
||||
const dataList = ref<DictDataVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const defaultDictType = ref('');
|
||||
const typeOptions = ref<DictTypeVO[]>([]);
|
||||
|
||||
const dataFormRef = ref<ElFormInstance>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 数据标签回显样式
|
||||
const listClassOptions = ref<Array<{ value: string; label: string }>>([
|
||||
{ value: 'default', label: '默认' },
|
||||
{ value: 'primary', label: '主要' },
|
||||
{ value: 'success', label: '成功' },
|
||||
{ value: 'info', label: '信息' },
|
||||
{ value: 'warning', label: '警告' },
|
||||
{ value: 'danger', label: '危险' }
|
||||
]);
|
||||
|
||||
const initFormData: DictDataForm = {
|
||||
dictCode: undefined,
|
||||
dictLabel: '',
|
||||
dictValue: '',
|
||||
cssClass: '',
|
||||
listClass: 'primary',
|
||||
dictSort: 0,
|
||||
remark: ''
|
||||
};
|
||||
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
dictLabel: ''
|
||||
},
|
||||
rules: {
|
||||
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
||||
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
||||
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询字典类型详细 */
|
||||
const getTypes = async (dictId: string | number) => {
|
||||
const { data } = await getType(dictId);
|
||||
queryParams.value.dictType = data.dictType;
|
||||
defaultDictType.value = data.dictType;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 查询字典类型列表 */
|
||||
const getTypeList = async () => {
|
||||
const res = await getDictOptionselect();
|
||||
typeOptions.value = res.data;
|
||||
};
|
||||
/** 查询字典数据列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listData(queryParams.value);
|
||||
dataList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
dataFormRef.value?.resetFields();
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
/** 返回按钮操作 */
|
||||
const handleClose = () => {
|
||||
const obj: RouteLocationNormalized = {
|
||||
fullPath: '',
|
||||
hash: '',
|
||||
matched: [],
|
||||
meta: undefined,
|
||||
name: undefined,
|
||||
params: undefined,
|
||||
query: undefined,
|
||||
redirectedFrom: undefined,
|
||||
path: '/system/dict'
|
||||
};
|
||||
proxy?.$tab.closeOpenPage(obj);
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value.dictType = defaultDictType.value;
|
||||
handleQuery();
|
||||
};
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
form.value.dictType = queryParams.value.dictType;
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加字典数据';
|
||||
};
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: DictDataVO[]) => {
|
||||
ids.value = selection.map((item) => item.dictCode);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: DictDataVO) => {
|
||||
reset();
|
||||
const dictCode = row?.dictCode || ids.value[0];
|
||||
const res = await getData(dictCode);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改字典数据';
|
||||
};
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
form.value.dictCode ? await updateData(form.value) : await addData(form.value);
|
||||
useDictStore().removeDict(queryParams.value.dictType);
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: DictDataVO) => {
|
||||
const dictCodes = row?.dictCode || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
|
||||
await delData(dictCodes);
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
useDictStore().removeDict(queryParams.value.dictType);
|
||||
};
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'system/dict/data/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`dict_data_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTypes(route.params && (route.params.dictId as string));
|
||||
getTypeList();
|
||||
});
|
||||
</script>
|
||||
@@ -1,106 +1,274 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="字典名称" prop="dictName">
|
||||
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典类型" prop="dictType">
|
||||
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
|
||||
删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<template>
|
||||
<div class="p-2 dict-page">
|
||||
<el-row :gutter="16" class="dict-grid">
|
||||
<!-- 字典类型 -->
|
||||
<el-col :xs="24" :lg="12">
|
||||
<el-card shadow="hover" class="dict-card">
|
||||
<template #header>
|
||||
<div class="dict-card__header">
|
||||
<div class="dict-card__title">字典管理</div>
|
||||
<right-toolbar v-model:show-search="showTypeSearch" @query-table="getTypeList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="showTypeSearch" class="dict-form-scroll">
|
||||
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true">
|
||||
<el-form-item label="字典名称" prop="dictName">
|
||||
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典类型" prop="dictType">
|
||||
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="dict-actions">
|
||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">新增</el-button>
|
||||
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()"
|
||||
>修改</el-button
|
||||
>
|
||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="typeMultiple" @click="handleTypeDelete()"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleTypeExport">导出</el-button>
|
||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
|
||||
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
|
||||
<span>{{ scope.row.dictType }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="dict-table-wrap">
|
||||
<el-table
|
||||
ref="typeTableRef"
|
||||
v-loading="typeLoading"
|
||||
border
|
||||
:data="typeList"
|
||||
highlight-current-row
|
||||
@row-click="handleTypeRowClick"
|
||||
@selection-change="handleTypeSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
|
||||
<el-table-column label="字典名称" align="center" prop="dictName" width="120" />
|
||||
<el-table-column label="字典类型" align="center" prop="dictType" width="160">
|
||||
<template #default="scope">
|
||||
<span class="link-type" @click.stop="handleTypeRowClick(scope.row)">{{ scope.row.dictType }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" width="160"/>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleTypeUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleTypeDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改参数配置对话框 -->
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
||||
<el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<pagination
|
||||
v-show="typeTotal > 0"
|
||||
v-model:page="typeQueryParams.pageNum"
|
||||
v-model:limit="typeQueryParams.pageSize"
|
||||
:total="typeTotal"
|
||||
@pagination="getTypeList"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 字典数据 -->
|
||||
<el-col :xs="24" :lg="12">
|
||||
<el-card shadow="hover" class="dict-card">
|
||||
<template #header>
|
||||
<div class="dict-card__header">
|
||||
<div class="dict-card__title">
|
||||
字典数据
|
||||
<span class="dict-card__subtitle">{{ currentDictLabel }}</span>
|
||||
</div>
|
||||
<right-toolbar v-model:show-search="showDataSearch" @query-table="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="showDataSearch" class="dict-form-scroll">
|
||||
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true">
|
||||
<el-form-item label="字典标签" prop="dictLabel">
|
||||
<el-input
|
||||
v-model="dataQueryParams.dictLabel"
|
||||
placeholder="请输入字典标签"
|
||||
clearable
|
||||
:disabled="!hasCurrentDict"
|
||||
@keyup.enter="handleDataQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" :disabled="!hasCurrentDict" @click="handleDataQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" :disabled="!hasCurrentDict" @click="handleDataResetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="dict-actions">
|
||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd"
|
||||
>新增</el-button
|
||||
>
|
||||
<el-button
|
||||
v-hasPermi="['system:dict:edit']"
|
||||
type="success"
|
||||
plain
|
||||
icon="Edit"
|
||||
:disabled="dataSingle || !hasCurrentDict"
|
||||
@click="handleDataUpdate()"
|
||||
>修改</el-button
|
||||
>
|
||||
<el-button
|
||||
v-hasPermi="['system:dict:remove']"
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="dataMultiple || !hasCurrentDict"
|
||||
@click="handleDataDelete()"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" :disabled="!hasCurrentDict" @click="handleDataExport"
|
||||
>导出</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="dict-table-wrap">
|
||||
<el-table v-loading="dataLoading" border :data="dataList" @selection-change="handleDataSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
|
||||
<el-table-column label="字典标签" align="center" prop="dictLabel" width="80">
|
||||
<template #default="scope">
|
||||
<span
|
||||
v-if="
|
||||
(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)
|
||||
"
|
||||
>{{ scope.row.dictLabel }}</span
|
||||
>
|
||||
<el-tag
|
||||
v-else
|
||||
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
|
||||
:class="scope.row.cssClass"
|
||||
>{{ scope.row.dictLabel }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典键值" align="center" prop="dictValue" width="80" />
|
||||
<el-table-column label="字典排序" align="center" prop="dictSort" width="80" />
|
||||
<el-table-column label="备注" align="center" prop="remark" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleDataUpdate(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDataDelete(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="dataTotal > 0"
|
||||
v-model:page="dataQueryParams.pageNum"
|
||||
v-model:limit="dataQueryParams.pageSize"
|
||||
:total="dataTotal"
|
||||
@pagination="getDataList"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 字典类型对话框 -->
|
||||
<el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="500px" append-to-body>
|
||||
<el-form ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="100px">
|
||||
<el-form-item label="字典名称" prop="dictName">
|
||||
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
|
||||
<el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典类型" prop="dictType">
|
||||
<el-input v-model="form.dictType" placeholder="请输入字典类型" />
|
||||
<el-form-item prop="dictType">
|
||||
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
|
||||
<span slot="label">
|
||||
<el-tooltip content="数据存储中的Key值,如:sys_user_sex" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
字典类型
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
<el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitTypeForm">确 定</el-button>
|
||||
<el-button @click="cancelType">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 字典数据对话框 -->
|
||||
<el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" append-to-body>
|
||||
<el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="80px">
|
||||
<el-form-item label="字典类型">
|
||||
<el-input v-model="dataForm.dictType" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据标签" prop="dictLabel">
|
||||
<el-input v-model="dataForm.dictLabel" placeholder="请输入数据标签" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据键值" prop="dictValue">
|
||||
<el-input v-model="dataForm.dictValue" placeholder="请输入数据键值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="样式属性" prop="cssClass">
|
||||
<el-input v-model="dataForm.cssClass" placeholder="请输入样式属性" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示排序" prop="dictSort">
|
||||
<el-input-number v-model="dataForm.dictSort" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回显样式" prop="listClass">
|
||||
<el-select v-model="dataForm.listClass">
|
||||
<el-option
|
||||
v-for="item in listClassOptions"
|
||||
:key="item.value"
|
||||
:label="item.label + '(' + item.value + ')'"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitDataForm">确 定</el-button>
|
||||
<el-button @click="cancelData">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -110,35 +278,39 @@
|
||||
<script setup name="Dict" lang="ts">
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
|
||||
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
|
||||
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
|
||||
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const typeList = ref<DictTypeVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<number | string>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const typeLoading = ref(true);
|
||||
const showTypeSearch = ref(true);
|
||||
const typeIds = ref<Array<number | string>>([]);
|
||||
const typeSingle = ref(true);
|
||||
const typeMultiple = ref(true);
|
||||
const typeTotal = ref(0);
|
||||
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
|
||||
|
||||
const dictFormRef = ref<ElFormInstance>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const typeFormRef = ref<ElFormInstance>();
|
||||
const typeQueryFormRef = ref<ElFormInstance>();
|
||||
const typeTableRef = ref<ElTableInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
const typeDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: DictTypeForm = {
|
||||
const typeInitFormData: DictTypeForm = {
|
||||
dictId: undefined,
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
remark: ''
|
||||
};
|
||||
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
||||
form: { ...initFormData },
|
||||
|
||||
const typeState = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
||||
form: { ...typeInitFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -151,96 +323,353 @@ const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
const { queryParams: typeQueryParams, form: typeForm, rules: typeRules } = toRefs(typeState);
|
||||
|
||||
/** 查询字典类型列表 */
|
||||
const getList = () => {
|
||||
loading.value = true;
|
||||
listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
|
||||
const currentDict = ref<DictTypeVO | null>(null);
|
||||
const hasCurrentDict = computed(() => !!currentDict.value);
|
||||
const currentDictLabel = computed(() => {
|
||||
if (!currentDict.value) return '请先选择字典';
|
||||
return `${currentDict.value.dictName} / ${currentDict.value.dictType}`;
|
||||
});
|
||||
|
||||
const dataList = ref<DictDataVO[]>([]);
|
||||
const dataLoading = ref(false);
|
||||
const showDataSearch = ref(true);
|
||||
const dataIds = ref<Array<string | number>>([]);
|
||||
const dataSingle = ref(true);
|
||||
const dataMultiple = ref(true);
|
||||
const dataTotal = ref(0);
|
||||
|
||||
const dataFormRef = ref<ElFormInstance>();
|
||||
const dataQueryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dataDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const listClassOptions = ref<Array<{ value: string; label: string }>>([
|
||||
{ value: 'default', label: '默认' },
|
||||
{ value: 'primary', label: '主要' },
|
||||
{ value: 'success', label: '成功' },
|
||||
{ value: 'info', label: '信息' },
|
||||
{ value: 'warning', label: '警告' },
|
||||
{ value: 'danger', label: '危险' }
|
||||
]);
|
||||
|
||||
const dataInitFormData: DictDataForm = {
|
||||
dictCode: undefined,
|
||||
dictLabel: '',
|
||||
dictValue: '',
|
||||
cssClass: '',
|
||||
listClass: 'primary',
|
||||
dictSort: 0,
|
||||
remark: ''
|
||||
};
|
||||
|
||||
const dataState = reactive<PageData<DictDataForm, DictDataQuery>>({
|
||||
form: { ...dataInitFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
dictLabel: ''
|
||||
},
|
||||
rules: {
|
||||
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
||||
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
||||
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRefs(dataState);
|
||||
|
||||
const getTypeList = () => {
|
||||
typeLoading.value = true;
|
||||
listType(proxy?.addDateRange(typeQueryParams.value, dateRange.value)).then((res) => {
|
||||
typeList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
typeTotal.value = res.total;
|
||||
typeLoading.value = false;
|
||||
ensureCurrentType();
|
||||
});
|
||||
};
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
|
||||
const ensureCurrentType = () => {
|
||||
if (!typeList.value.length) {
|
||||
currentDict.value = null;
|
||||
dataQueryParams.value.dictType = '';
|
||||
dataList.value = [];
|
||||
dataTotal.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const current = currentDict.value && typeList.value.find((item) => item.dictId === currentDict.value?.dictId);
|
||||
const nextRow = current || typeList.value[0];
|
||||
setCurrentType(nextRow);
|
||||
};
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
dictFormRef.value?.resetFields();
|
||||
|
||||
const setCurrentType = (row: DictTypeVO) => {
|
||||
currentDict.value = row;
|
||||
dataQueryParams.value.dictType = row.dictType;
|
||||
dataQueryParams.value.pageNum = 1;
|
||||
dataQueryParams.value.dictLabel = '';
|
||||
getDataList();
|
||||
nextTick(() => typeTableRef.value?.setCurrentRow(row));
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
|
||||
const handleTypeRowClick = (row: DictTypeVO) => {
|
||||
setCurrentType(row);
|
||||
};
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
|
||||
const cancelType = () => {
|
||||
resetTypeForm();
|
||||
typeDialog.visible = false;
|
||||
};
|
||||
|
||||
const resetTypeForm = () => {
|
||||
typeForm.value = { ...typeInitFormData };
|
||||
typeFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const handleTypeQuery = () => {
|
||||
typeQueryParams.value.pageNum = 1;
|
||||
getTypeList();
|
||||
};
|
||||
|
||||
const handleTypeResetQuery = () => {
|
||||
dateRange.value = ['', ''];
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
typeQueryFormRef.value?.resetFields();
|
||||
handleTypeQuery();
|
||||
};
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加字典类型';
|
||||
|
||||
const handleTypeAdd = () => {
|
||||
resetTypeForm();
|
||||
typeDialog.visible = true;
|
||||
typeDialog.title = '添加字典类型';
|
||||
};
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: DictTypeVO[]) => {
|
||||
ids.value = selection.map((item) => item.dictId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
|
||||
const handleTypeSelectionChange = (selection: DictTypeVO[]) => {
|
||||
typeIds.value = selection.map((item) => item.dictId);
|
||||
typeSingle.value = selection.length != 1;
|
||||
typeMultiple.value = !selection.length;
|
||||
};
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: DictTypeVO) => {
|
||||
reset();
|
||||
const dictId = row?.dictId || ids.value[0];
|
||||
|
||||
const handleTypeUpdate = async (row?: DictTypeVO) => {
|
||||
resetTypeForm();
|
||||
const dictId = row?.dictId || typeIds.value[0];
|
||||
const res = await getType(dictId);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改字典类型';
|
||||
Object.assign(typeForm.value, res.data);
|
||||
typeDialog.visible = true;
|
||||
typeDialog.title = '修改字典类型';
|
||||
};
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
dictFormRef.value?.validate(async (valid: boolean) => {
|
||||
|
||||
const submitTypeForm = () => {
|
||||
typeFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
form.value.dictId ? await updateType(form.value) : await addType(form.value);
|
||||
typeForm.value.dictId ? await updateType(typeForm.value) : await addType(typeForm.value);
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
getList();
|
||||
typeDialog.visible = false;
|
||||
getTypeList();
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: DictTypeVO) => {
|
||||
const dictIds = row?.dictId || ids.value;
|
||||
|
||||
const handleTypeDelete = async (row?: DictTypeVO) => {
|
||||
const dictIds = row?.dictId || typeIds.value;
|
||||
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
|
||||
await delType(dictIds);
|
||||
getList();
|
||||
getTypeList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
|
||||
const handleTypeExport = () => {
|
||||
proxy?.download(
|
||||
'system/dict/type/export',
|
||||
{
|
||||
...queryParams.value
|
||||
...typeQueryParams.value
|
||||
},
|
||||
`dict_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
/** 刷新缓存按钮操作 */
|
||||
|
||||
const handleRefreshCache = async () => {
|
||||
await refreshCache();
|
||||
proxy?.$modal.msgSuccess('刷新成功');
|
||||
useDictStore().cleanDict();
|
||||
};
|
||||
|
||||
const getDataList = async () => {
|
||||
if (!currentDict.value) {
|
||||
dataList.value = [];
|
||||
dataTotal.value = 0;
|
||||
dataLoading.value = false;
|
||||
return;
|
||||
}
|
||||
dataLoading.value = true;
|
||||
const res = await listData(dataQueryParams.value);
|
||||
dataList.value = res.rows;
|
||||
dataTotal.value = res.total;
|
||||
dataLoading.value = false;
|
||||
};
|
||||
|
||||
const cancelData = () => {
|
||||
dataDialog.visible = false;
|
||||
resetDataForm();
|
||||
};
|
||||
|
||||
const resetDataForm = () => {
|
||||
dataForm.value = { ...dataInitFormData };
|
||||
dataFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const handleDataQuery = () => {
|
||||
if (!currentDict.value) return;
|
||||
dataQueryParams.value.pageNum = 1;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const handleDataResetQuery = () => {
|
||||
dataQueryFormRef.value?.resetFields();
|
||||
dataQueryParams.value.dictLabel = '';
|
||||
handleDataQuery();
|
||||
};
|
||||
|
||||
const handleDataAdd = () => {
|
||||
if (!currentDict.value) {
|
||||
proxy?.$modal.msgWarning('请先选择字典');
|
||||
return;
|
||||
}
|
||||
resetDataForm();
|
||||
dataForm.value.dictType = currentDict.value.dictType;
|
||||
dataDialog.visible = true;
|
||||
dataDialog.title = '添加字典数据';
|
||||
};
|
||||
|
||||
const handleDataSelectionChange = (selection: DictDataVO[]) => {
|
||||
dataIds.value = selection.map((item) => item.dictCode);
|
||||
dataSingle.value = selection.length != 1;
|
||||
dataMultiple.value = !selection.length;
|
||||
};
|
||||
|
||||
const handleDataUpdate = async (row?: DictDataVO) => {
|
||||
if (!currentDict.value) {
|
||||
proxy?.$modal.msgWarning('请先选择字典');
|
||||
return;
|
||||
}
|
||||
resetDataForm();
|
||||
const dictCode = row?.dictCode || dataIds.value[0];
|
||||
const res = await getData(dictCode);
|
||||
Object.assign(dataForm.value, res.data);
|
||||
dataDialog.visible = true;
|
||||
dataDialog.title = '修改字典数据';
|
||||
};
|
||||
|
||||
const submitDataForm = () => {
|
||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
dataForm.value.dictCode ? await updateData(dataForm.value) : await addData(dataForm.value);
|
||||
useDictStore().removeDict(dataQueryParams.value.dictType);
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dataDialog.visible = false;
|
||||
await getDataList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDataDelete = async (row?: DictDataVO) => {
|
||||
if (!currentDict.value) {
|
||||
proxy?.$modal.msgWarning('请先选择字典');
|
||||
return;
|
||||
}
|
||||
const dictCodes = row?.dictCode || dataIds.value;
|
||||
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
|
||||
await delData(dictCodes);
|
||||
await getDataList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
useDictStore().removeDict(dataQueryParams.value.dictType);
|
||||
};
|
||||
|
||||
const handleDataExport = () => {
|
||||
if (!currentDict.value) {
|
||||
proxy?.$modal.msgWarning('请先选择字典');
|
||||
return;
|
||||
}
|
||||
proxy?.download(
|
||||
'system/dict/data/export',
|
||||
{
|
||||
...dataQueryParams.value
|
||||
},
|
||||
`dict_data_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getTypeList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dict-grid {
|
||||
row-gap: 16px;
|
||||
}
|
||||
|
||||
.dict-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dict-card__title {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dict-card__subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.dict-form-scroll {
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
margin-bottom: 12px;
|
||||
padding-right: 6px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.dict-form-scroll :deep(.el-form) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 12px;
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
.dict-form-scroll :deep(.el-form-item) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dict-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.dict-actions :deep(.el-button) {
|
||||
height: 32px;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.dict-actions :deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.dict-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
|
||||
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
<el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
@@ -39,8 +39,12 @@
|
||||
v-loading="loading"
|
||||
:data="menuList"
|
||||
row-key="menuId"
|
||||
border
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
:default-expand-all="false"
|
||||
lazy
|
||||
:load="getChildrenList"
|
||||
:expand-change="expandMenuHandle"
|
||||
>
|
||||
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
|
||||
<el-table-column prop="icon" label="图标" align="center" width="100">
|
||||
@@ -129,8 +133,8 @@
|
||||
</span>
|
||||
</template>
|
||||
<el-radio-group v-model="form.isFrame">
|
||||
<el-radio label="0">是</el-radio>
|
||||
<el-radio label="1">否</el-radio>
|
||||
<el-radio value="0">是</el-radio>
|
||||
<el-radio value="1">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -207,8 +211,8 @@
|
||||
</span>
|
||||
</template>
|
||||
<el-radio-group v-model="form.isCache">
|
||||
<el-radio label="0">缓存</el-radio>
|
||||
<el-radio label="1">不缓存</el-radio>
|
||||
<el-radio value="0">缓存</el-radio>
|
||||
<el-radio value="1">不缓存</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -225,7 +229,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<el-radio-group v-model="form.visible">
|
||||
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :label="dict.value">{{ dict.label }} </el-radio>
|
||||
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -242,12 +246,27 @@
|
||||
</span>
|
||||
</template>
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="form.visible !== '0'" :span="12">
|
||||
<el-form-item label="激活路径" prop="form.remark">
|
||||
<template #label>
|
||||
<span>
|
||||
<el-tooltip content="隐藏菜单填写默认激活路由,比如激活父菜单的路由 /system/user" placement="top">
|
||||
<el-icon>
|
||||
<question-filled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
激活路由
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.remark" placeholder="请输入激活路径" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -257,11 +276,31 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="750px">
|
||||
<el-tree
|
||||
ref="menuTreeRef"
|
||||
class="tree-border"
|
||||
:data="menuOptions"
|
||||
show-checkbox
|
||||
node-key="menuId"
|
||||
:check-strictly="false"
|
||||
empty-text="加载中,请稍候"
|
||||
:default-expanded-keys="[0]"
|
||||
:props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading">确 定</el-button>
|
||||
<el-button @click="cancelCascade">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Menu" lang="ts">
|
||||
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
|
||||
import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
|
||||
import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
|
||||
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
|
||||
|
||||
@@ -275,10 +314,11 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
|
||||
|
||||
const menuList = ref<MenuVO[]>([]);
|
||||
const menuChildrenListMap = ref({});
|
||||
const menuExpandMap = ref({});
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const menuOptions = ref<MenuOptionsType[]>([]);
|
||||
const isExpandAll = ref(false);
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
@@ -316,14 +356,73 @@ const data = reactive<PageData<MenuForm, MenuQuery>>({
|
||||
const menuTableRef = ref<ElTableInstance>();
|
||||
|
||||
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data);
|
||||
|
||||
/** 获取子菜单列表 */
|
||||
const getChildrenList = async (row: any, treeNode: unknown, resolve: (data: any[]) => void) => {
|
||||
menuExpandMap.value[row.menuId] = { row, treeNode, resolve };
|
||||
const children = menuChildrenListMap.value[row.menuId] || [];
|
||||
// 菜单的子菜单清空后关闭展开
|
||||
if (children.length == 0) {
|
||||
// fix: 处理当菜单只有一个子菜单并被删除,需要将父菜单的展开状态关闭
|
||||
menuTableRef.value?.updateKeyChildren(row.menuId, children);
|
||||
}
|
||||
resolve(children);
|
||||
};
|
||||
|
||||
/** 收起菜单时从menuExpandMap中删除对应菜单id数据 */
|
||||
const expandMenuHandle = async (row: any, expanded: boolean) => {
|
||||
if (!expanded) {
|
||||
menuExpandMap.value[row.menuId] = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/** 刷新展开的菜单数据 */
|
||||
const refreshLoadTree = (parentId: string | number) => {
|
||||
if (menuExpandMap.value[parentId]) {
|
||||
const { row, treeNode, resolve } = menuExpandMap.value[parentId];
|
||||
if (row) {
|
||||
getChildrenList(row, treeNode, resolve);
|
||||
if (row.parentId) {
|
||||
const grandpaMenu = menuExpandMap.value[row.parentId];
|
||||
getChildrenList(grandpaMenu.row, grandpaMenu.treeNode, grandpaMenu.resolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 重新加载所有已展开的菜单的数据 */
|
||||
const refreshAllExpandMenuData = () => {
|
||||
for (const menuId in menuExpandMap.value) {
|
||||
refreshLoadTree(menuId);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查询菜单列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listMenu(queryParams.value);
|
||||
const data = proxy?.handleTree<MenuVO>(res.data, 'menuId');
|
||||
if (data) {
|
||||
menuList.value = data;
|
||||
|
||||
const tempMap = {};
|
||||
// 存储 父菜单:子菜单列表
|
||||
for (const menu of res.data) {
|
||||
const parentId = menu.parentId;
|
||||
if (!tempMap[parentId]) {
|
||||
tempMap[parentId] = [];
|
||||
}
|
||||
tempMap[parentId].push(menu);
|
||||
}
|
||||
// 创建一个当前所有 menuId 的 Set,用于查找父菜单是否存在于当前数据中
|
||||
const menuIdSet = new Set();
|
||||
// 设置有没有子菜单
|
||||
for (const menu of res.data) {
|
||||
menu['hasChildren'] = tempMap[menu.menuId]?.length > 0;
|
||||
menuIdSet.add(menu.menuId);
|
||||
}
|
||||
menuChildrenListMap.value = tempMap;
|
||||
// 找出所有父ID不在当前菜单ID集合中的菜单项,作为新的顶层菜单
|
||||
menuList.value = res.data.filter((menu) => !menuIdSet.has(menu.parentId));
|
||||
// 根据新数据重新加载子菜单数据
|
||||
refreshAllExpandMenuData();
|
||||
loading.value = false;
|
||||
};
|
||||
/** 查询菜单下拉树结构 */
|
||||
@@ -362,18 +461,6 @@ const handleAdd = (row?: MenuVO) => {
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加菜单';
|
||||
};
|
||||
/** 展开/折叠操作 */
|
||||
const handleToggleExpandAll = () => {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
toggleExpandAll(menuList.value, isExpandAll.value);
|
||||
};
|
||||
/** 展开/折叠所有 */
|
||||
const toggleExpandAll = (data: MenuVO[], status: boolean) => {
|
||||
data.forEach((item: MenuVO) => {
|
||||
menuTableRef.value?.toggleRowExpansion(item, status);
|
||||
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
|
||||
});
|
||||
};
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: MenuVO) => {
|
||||
reset();
|
||||
@@ -404,7 +491,50 @@ const handleDelete = async (row: MenuVO) => {
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
const deleteLoading = ref<boolean>(false);
|
||||
const menuTreeRef = ref<ElTreeInstance>();
|
||||
|
||||
const deleteDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: '级联删除菜单'
|
||||
});
|
||||
|
||||
/** 级联删除按钮操作 */
|
||||
const handleCascadeDelete = () => {
|
||||
menuTreeRef.value?.setCheckedKeys([]);
|
||||
getTreeselect();
|
||||
deleteDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancelCascade = () => {
|
||||
menuTreeRef.value?.setCheckedKeys([]);
|
||||
deleteDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 删除提交按钮 */
|
||||
const submitDeleteForm = async () => {
|
||||
const menuIds = menuTreeRef.value?.getCheckedKeys();
|
||||
if (menuIds.length < 0) {
|
||||
proxy?.$modal.msgWarning('请选择要删除的菜单');
|
||||
return;
|
||||
}
|
||||
|
||||
deleteLoading.value = true;
|
||||
await cascadeDelMenu(menuIds).finally(() => (deleteLoading.value = false));
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
deleteDialog.visible = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tree-border {
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="noticeList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
|
||||
<el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="ossConfigList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
|
||||
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
|
||||
@@ -87,10 +87,18 @@
|
||||
<el-input v-model="form.configKey" placeholder="请输入配置key" />
|
||||
</el-form-item>
|
||||
<el-form-item label="访问站点" prop="endpoint">
|
||||
<el-input v-model="form.endpoint" placeholder="请输入访问站点" />
|
||||
<el-input v-model="form.endpoint" placeholder="请输入访问站点">
|
||||
<template #prefix>
|
||||
<span style="color: #999">{{ protocol }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义域名" prop="domain">
|
||||
<el-input v-model="form.domain" placeholder="请输入自定义域名" />
|
||||
<el-input v-model="form.domain" placeholder="请输入自定义域名">
|
||||
<template #prefix>
|
||||
<span style="color: #999">{{ protocol }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKey" prop="accessKey">
|
||||
<el-input v-model="form.accessKey" placeholder="请输入accessKey" />
|
||||
@@ -239,6 +247,8 @@ const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
const protocol = computed(() => (form.value.isHttps === 'Y' ? 'https://' : 'http://'));
|
||||
|
||||
/** 查询对象存储配置列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
@@ -306,7 +316,7 @@ const submitForm = () => {
|
||||
};
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: OssConfigVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
|
||||
await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
v-if="showTable"
|
||||
v-loading="loading"
|
||||
:data="ossList"
|
||||
border
|
||||
:header-cell-class-name="handleHeaderClass"
|
||||
@selection-change="handleSelectionChange"
|
||||
@header-click="handleHeaderCLick"
|
||||
@@ -255,9 +256,9 @@ const handleHeaderCLick = (column: any) => {
|
||||
handleOrderChange(column.property, column.multiOrder);
|
||||
};
|
||||
const handleOrderChange = (prop: string, order: string) => {
|
||||
let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
|
||||
let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
|
||||
let propIndex = orderByArr.indexOf(prop);
|
||||
const orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
|
||||
const isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
|
||||
const propIndex = orderByArr.indexOf(prop);
|
||||
if (propIndex !== -1) {
|
||||
if (order) {
|
||||
//排序里已存在 只修改排序
|
||||
@@ -306,7 +307,7 @@ const handleDownload = (row: OssVO) => {
|
||||
};
|
||||
/** 预览开关按钮 */
|
||||
const handlePreviewListResource = async (preview: boolean) => {
|
||||
let text = preview ? '启用' : '停用';
|
||||
const text = preview ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
|
||||
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="postList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
|
||||
<el-table-column label="岗位编码" align="center" prop="postCode" />
|
||||
@@ -170,10 +170,9 @@
|
||||
</template>
|
||||
|
||||
<script setup name="Post" lang="ts">
|
||||
import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
|
||||
import { listPost, addPost, delPost, getPost, updatePost, deptTreeSelect } from '@/api/system/post';
|
||||
import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
|
||||
import { DeptVO } from '@/api/system/dept/types';
|
||||
import api from '@/api/system/user';
|
||||
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
|
||||
@@ -186,7 +185,7 @@ const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const deptName = ref('');
|
||||
const deptOptions = ref<DeptVO[]>([]);
|
||||
const deptOptions = ref<DeptTreeVO[]>([]);
|
||||
const deptTreeRef = ref<ElTreeInstance>();
|
||||
const postFormRef = ref<ElFormInstance>();
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
@@ -212,6 +211,8 @@ const data = reactive<PageData<PostForm, PostQuery>>({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deptId: undefined,
|
||||
belongDeptId: undefined,
|
||||
postCode: '',
|
||||
postName: '',
|
||||
postCategory: '',
|
||||
@@ -245,7 +246,7 @@ watchEffect(
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
const getTreeSelect = async () => {
|
||||
const res = await api.deptTreeSelect();
|
||||
const res = await deptTreeSelect();
|
||||
deptOptions.value = res.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
|
||||
<el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
@@ -55,7 +55,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
|
||||
<el-table ref="roleTableRef" border v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
|
||||
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
|
||||
@@ -323,7 +323,7 @@ const handleSelectionChange = (selection: RoleVO[]) => {
|
||||
|
||||
/** 角色状态修改 */
|
||||
const handleStatusChange = async (row: RoleVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
|
||||
await changeRoleStatus(row.roleId, row.status);
|
||||
@@ -346,11 +346,11 @@ const getMenuTreeselect = async () => {
|
||||
/** 所有部门节点数据 */
|
||||
const getDeptAllCheckedKeys = (): any => {
|
||||
// 目前被选中的部门节点
|
||||
let checkedKeys = deptRef.value?.getCheckedKeys();
|
||||
const checkedKeys = deptRef.value?.getCheckedKeys();
|
||||
// 半选中的部门节点
|
||||
let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
|
||||
const halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
|
||||
if (halfCheckedKeys) {
|
||||
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
|
||||
checkedKeys?.unshift(...halfCheckedKeys);
|
||||
}
|
||||
return checkedKeys;
|
||||
};
|
||||
@@ -404,14 +404,14 @@ const getRoleDeptTreeSelect = async (roleId: string | number) => {
|
||||
/** 树权限(展开/折叠)*/
|
||||
const handleCheckedTreeExpand = (value: boolean, type: string) => {
|
||||
if (type == 'menu') {
|
||||
let treeList = menuOptions.value;
|
||||
const treeList = menuOptions.value;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
if (menuRef.value) {
|
||||
menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
|
||||
}
|
||||
}
|
||||
} else if (type == 'dept') {
|
||||
let treeList = deptOptions.value;
|
||||
const treeList = deptOptions.value;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
if (deptRef.value) {
|
||||
deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
|
||||
@@ -438,11 +438,11 @@ const handleCheckedTreeConnect = (value: any, type: string) => {
|
||||
/** 所有菜单节点数据 */
|
||||
const getMenuAllCheckedKeys = (): any => {
|
||||
// 目前被选中的菜单节点
|
||||
let checkedKeys = menuRef.value?.getCheckedKeys();
|
||||
const checkedKeys = menuRef.value?.getCheckedKeys();
|
||||
// 半选中的菜单节点
|
||||
let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
|
||||
const halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
|
||||
if (halfCheckedKeys) {
|
||||
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
|
||||
checkedKeys?.unshift(...halfCheckedKeys);
|
||||
}
|
||||
return checkedKeys;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<el-table ref="tableRef" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
|
||||
<el-table ref="tableRef" border :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55"></el-table-column>
|
||||
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
|
||||
|
||||
@@ -47,11 +47,14 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantConfig">同步租户参数配置</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="tenantList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="id" align="center" prop="id" />
|
||||
<el-table-column label="租户编号" align="center" prop="tenantId" />
|
||||
@@ -152,7 +155,8 @@ import {
|
||||
updateTenant,
|
||||
changeTenantStatus,
|
||||
syncTenantPackage,
|
||||
syncTenantDict
|
||||
syncTenantDict,
|
||||
syncTenantConfig
|
||||
} from '@/api/system/tenant';
|
||||
import { selectTenantPackage } from '@/api/system/tenantPackage';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -245,7 +249,7 @@ const getList = async () => {
|
||||
|
||||
// 租户套餐状态修改
|
||||
const handleStatusChange = async (row: TenantVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
|
||||
await changeTenantStatus(row.id, row.tenantId, row.status);
|
||||
@@ -361,7 +365,14 @@ const handleExport = () => {
|
||||
/**同步租户字典*/
|
||||
const handleSyncTenantDict = async () => {
|
||||
await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
|
||||
let res = await syncTenantDict();
|
||||
const res = await syncTenantDict();
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
};
|
||||
|
||||
/**同步租户参数配置*/
|
||||
const handleSyncTenantConfig = async () => {
|
||||
await proxy?.$modal.confirm('确认要同步所有租户参数配置吗?');
|
||||
const res = await syncTenantConfig();
|
||||
proxy?.$modal.msgSuccess(res.msg);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="tenantPackageList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="tenantPackageList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="false" label="租户套餐id" align="center" prop="packageId" />
|
||||
<el-table-column label="套餐名称" align="center" prop="packageName" />
|
||||
@@ -108,7 +108,7 @@ import {
|
||||
updateTenantPackage,
|
||||
changePackageStatus
|
||||
} from '@/api/system/tenantPackage';
|
||||
import { treeselect as menuTreeselect, tenantPackageMenuTreeselect } from '@/api/system/menu';
|
||||
import { tenantPackageMenuTreeselect } from '@/api/system/menu';
|
||||
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from '@/api/system/tenantPackage/types';
|
||||
import { MenuTreeOption } from '@/api/system/menu/types';
|
||||
import to from 'await-to-js';
|
||||
@@ -158,20 +158,14 @@ const data = reactive<PageData<TenantPkgForm, TenantPkgQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询菜单树结构 */
|
||||
const getMenuTreeselect = async () => {
|
||||
const { data } = await menuTreeselect();
|
||||
menuOptions.value = data;
|
||||
};
|
||||
|
||||
// 所有菜单节点数据
|
||||
const getMenuAllCheckedKeys = (): any => {
|
||||
// 目前被选中的菜单节点
|
||||
let checkedKeys = menuTreeRef.value?.getCheckedKeys();
|
||||
const checkedKeys = menuTreeRef.value?.getCheckedKeys();
|
||||
// 半选中的菜单节点
|
||||
let halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
|
||||
const halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
|
||||
if (halfCheckedKeys) {
|
||||
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
|
||||
checkedKeys?.unshift(...halfCheckedKeys);
|
||||
}
|
||||
return checkedKeys;
|
||||
};
|
||||
@@ -194,7 +188,7 @@ const getList = async () => {
|
||||
|
||||
// 租户套餐状态修改
|
||||
const handleStatusChange = async (row: TenantPkgVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
const [err] = await to(proxy?.$modal.confirm('确认要"' + text + '""' + row.packageName + '"套餐吗?') as Promise<any>);
|
||||
if (err) {
|
||||
row.status = row.status === '0' ? '1' : '0';
|
||||
@@ -241,7 +235,7 @@ const handleSelectionChange = (selection: TenantPkgVO[]) => {
|
||||
// 树权限(展开/折叠)
|
||||
const handleCheckedTreeExpand = (value: CheckboxValueType, type: string) => {
|
||||
if (type == 'menu') {
|
||||
let treeList = menuOptions.value;
|
||||
const treeList = menuOptions.value;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
if (menuTreeRef.value) {
|
||||
menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value as boolean;
|
||||
@@ -265,9 +259,9 @@ const handleCheckedTreeConnect = (value: CheckboxValueType, type: string) => {
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
const handleAdd = async () => {
|
||||
reset();
|
||||
getMenuTreeselect();
|
||||
await getPackageMenuTreeselect(0);
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加租户套餐';
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
border
|
||||
:row-key="getRowKey"
|
||||
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
|
||||
@row-click="clickRow"
|
||||
@@ -33,7 +34,7 @@
|
||||
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
|
||||
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
|
||||
<el-table-column label="角色编号" align="center" prop="roleId" />
|
||||
<el-table-column label="角色名称" align="center" prop="roleName" />
|
||||
<el-table-column label="权限字符" align="center" prop="roleKey" />
|
||||
@@ -80,8 +81,10 @@ const tableRef = ref<ElTableInstance>();
|
||||
|
||||
/** 单击选中行数据 */
|
||||
const clickRow = (row: RoleVO) => {
|
||||
row.flag = !row.flag;
|
||||
tableRef.value?.toggleRowSelection(row, row.flag);
|
||||
if (checkSelectable(row)) {
|
||||
row.flag = !row.flag;
|
||||
tableRef.value?.toggleRowSelection(row, row.flag);
|
||||
}
|
||||
};
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: RoleVO[]) => {
|
||||
@@ -91,6 +94,10 @@ const handleSelectionChange = (selection: RoleVO[]) => {
|
||||
const getRowKey = (row: RoleVO): string => {
|
||||
return String(row.roleId);
|
||||
};
|
||||
/** 检查角色状态 */
|
||||
const checkSelectable = (row: RoleVO): boolean => {
|
||||
return row.status === '0';
|
||||
};
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
const obj: RouteLocationNormalized = {
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
@@ -92,7 +95,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
|
||||
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
||||
@@ -151,7 +154,7 @@
|
||||
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
|
||||
<el-form-item label="归属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="form.deptId"
|
||||
@@ -206,7 +209,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
|
||||
<el-form-item label="岗位">
|
||||
<el-select v-model="form.postIds" multiple placeholder="请选择">
|
||||
<el-option
|
||||
@@ -219,7 +222,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
|
||||
<el-form-item label="角色" prop="roleIds">
|
||||
<el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
|
||||
<el-option
|
||||
@@ -290,13 +293,12 @@ import api from '@/api/system/user';
|
||||
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
|
||||
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
|
||||
import { RoleVO } from '@/api/system/role/types';
|
||||
import { PostQuery, PostVO } from '@/api/system/post/types';
|
||||
import { treeselect } from '@/api/system/dept';
|
||||
import { PostVO } from '@/api/system/post/types';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import { to } from 'await-to-js';
|
||||
import { optionselect } from '@/api/system/post';
|
||||
import { hasPermi } from '@/directive/permission';
|
||||
import { checkPermi } from '@/utils/permission';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -408,7 +410,7 @@ const initData: PageData<UserForm, UserQuery> = {
|
||||
],
|
||||
phonenumber: [
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
pattern: /^1[3456789][0-9]\d{8}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
@@ -498,7 +500,7 @@ const handleDelete = async (row?: UserVO) => {
|
||||
|
||||
/** 用户状态修改 */
|
||||
const handleStatusChange = async (row: UserVO) => {
|
||||
let text = row.status === '0' ? '启用' : '停用';
|
||||
const text = row.status === '0' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
|
||||
await api.changeUserStatus(row.userId, row.status);
|
||||
@@ -613,7 +615,9 @@ const handleUpdate = async (row?: UserForm) => {
|
||||
dialog.title = '修改用户';
|
||||
Object.assign(form.value, data.user);
|
||||
postOptions.value = data.posts;
|
||||
roleOptions.value = data.roles;
|
||||
roleOptions.value = Array.from(
|
||||
new Map([...data.roles, ...data.user.roles].map(role => [role.roleId, role])).values()
|
||||
);
|
||||
form.value.postIds = data.postIds;
|
||||
form.value.roleIds = data.roleIds;
|
||||
form.value.password = '';
|
||||
@@ -623,7 +627,17 @@ const handleUpdate = async (row?: UserForm) => {
|
||||
const submitForm = () => {
|
||||
userFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
|
||||
if (form.value.userId) {
|
||||
// 自己编辑自己的情况下 不允许编辑角色部门岗位
|
||||
if (form.value.userId == useUserStore().userId) {
|
||||
form.value.roleIds = null;
|
||||
form.value.deptId = null;
|
||||
form.value.postIds = null;
|
||||
}
|
||||
await api.updateUser(form.value);
|
||||
} else {
|
||||
await api.addUser(form.value);
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user