mirror of
https://gitee.com/log4j/pig-ui.git
synced 2024-12-30 17:01:14 +08:00
🔖 Releasing / Version tags. 3.7.0-snapshot
This commit is contained in:
parent
7bee6535ea
commit
2ef280281c
115
README.md
Normal file
115
README.md
Normal file
@ -0,0 +1,115 @@
|
||||
### 核心依赖
|
||||
|
||||
| 依赖 | 版本 |
|
||||
| ---------------------- |----------------|
|
||||
| Spring Boot | 3.1.1 |
|
||||
| Spring Cloud | 2022.0.3 |
|
||||
| Spring Cloud Alibaba | 2022.0.0.0-RC2 |
|
||||
| Spring Authorization Server | 1.1.1 |
|
||||
| Mybatis Plus | 3.5.3.1 |
|
||||
| hutool | 5.8.20 |
|
||||
|
||||
### 模块说明
|
||||
|
||||
```lua
|
||||
pig-ui -- https://gitee.com/log4j/pig-ui
|
||||
|
||||
pig
|
||||
├── pig-auth -- 授权服务提供[3000]
|
||||
└── pig-common -- 系统公共模块
|
||||
├── pig-common-bom -- 全局依赖管理控制
|
||||
├── pig-common-core -- 公共工具类核心包
|
||||
├── pig-common-datasource -- 动态数据源包
|
||||
├── pig-common-job -- xxl-job 封装
|
||||
├── pig-common-log -- 日志服务
|
||||
├── pig-common-mybatis -- mybatis 扩展封装
|
||||
├── pig-common-seata -- 分布式事务
|
||||
├── pig-common-security -- 安全工具类
|
||||
├── pig-common-swagger -- 接口文档
|
||||
├── pig-common-feign -- feign 扩展封装
|
||||
└── pig-common-xss -- xss 安全封装
|
||||
├── pig-register -- Nacos Server[8848]
|
||||
├── pig-gateway -- Spring Cloud Gateway网关[9999]
|
||||
└── pig-upms -- 通用用户权限管理模块
|
||||
└── pig-upms-api -- 通用用户权限管理系统公共api模块
|
||||
└── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000]
|
||||
└── pig-visual
|
||||
└── pig-monitor -- 服务监控 [5001]
|
||||
└── pig-codegen -- 图形化代码生成 [5002]
|
||||
```
|
||||
|
||||
### 本地开发 运行
|
||||
|
||||
pig 提供了详细的[部署文档 wiki.pigx.vip](https://www.yuque.com/pig4cloud/pig/vsdox9),包括开发环境安装、服务端代码运行、前端代码运行等。
|
||||
|
||||
请务必**完全按照**文档部署运行章节 进行操作,减少踩坑弯路!!
|
||||
|
||||
### 定制自己微服务
|
||||
|
||||
[PIG DIY](https://pig4cloud.com/#/common/diy)
|
||||
|
||||
[PIG ARCHETYPE](https://pig4cloud.com/#/common/archetype)
|
||||
|
||||
### Docker 运行
|
||||
|
||||
```
|
||||
# 下载并运行服务端代码
|
||||
git clone https://gitee.com/log4j/pig.git
|
||||
|
||||
cd pig && mvn clean install && docker-compose up -d
|
||||
|
||||
# 下载并运行前端UI
|
||||
git clone https://gitee.com/log4j/pig-ui.git
|
||||
|
||||
cd pig-ui && npm install -g cnpm --registry=https://registry.npm.taobao.org
|
||||
|
||||
|
||||
cnpm install && cnpm run build:docker && cd docker && docker-compose up -d
|
||||
```
|
||||
|
||||
## 免费公开课
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><a href="https://www.bilibili.com/video/av45084065" target="_blank"><img src="https://minio.pigx.vip/oss/1655474345.jpg"></a></td>
|
||||
<td><a href="https://www.bilibili.com/video/av77344954" target="_blank"><img src="https://minio.pigx.vip/oss/1656837143.jpg"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://www.bilibili.com/video/BV1J5411476V" target="_blank"><img src="https://minio.pigx.vip/oss/1655474369.jpg"></a></td>
|
||||
<td><a href="https://www.bilibili.com/video/BV14p4y197K5" target="_blank"><img src="https://minio.pigx.vip/oss/1655474381.jpg"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 开源共建
|
||||
|
||||
### 开源协议
|
||||
|
||||
pig 开源软件遵循 [Apache 2.0 协议](https://www.apache.org/licenses/LICENSE-2.0.html)。
|
||||
允许商业使用,但务必保留类作者、Copyright 信息。
|
||||
|
||||
![](https://minio.pigx.vip/oss/1655474288.jpg)
|
||||
|
||||
|
||||
### 其他说明
|
||||
|
||||
1. 欢迎提交 [PR](https://dwz.cn/2KURd5Vf),注意对应提交对应 `dev` 分支
|
||||
代码规范 [spring-javaformat](https://github.com/spring-io/spring-javaformat)
|
||||
|
||||
<details>
|
||||
<summary>代码规范说明</summary>
|
||||
|
||||
1. 由于 <a href="https://github.com/spring-io/spring-javaformat" target="_blank">spring-javaformat</a>
|
||||
强制所有代码按照指定格式排版,未按此要求提交的代码将不能通过合并(打包)
|
||||
2. 如果使用 IntelliJ IDEA
|
||||
开发,请安装自动格式化软件 <a href="https://repo1.maven.org/maven2/io/spring/javaformat/spring-javaformat-intellij-idea-plugin/" target="_blank">
|
||||
spring-javaformat-intellij-idea-plugin</a>
|
||||
3. 其他开发工具,请参考 <a href="https://github.com/spring-io/spring-javaformat" target="_blank">spring-javaformat</a>
|
||||
说明,或`提交代码前`在项目根目录运行下列命令(需要开发者电脑支持`mvn`命令)进行代码格式化
|
||||
```
|
||||
mvn spring-javaformat:apply
|
||||
```
|
||||
</details>
|
||||
|
||||
2. 欢迎提交 [issue](https://gitee.com/log4j/pig/issues),请写清楚遇到问题的原因、开发环境、复显步骤。
|
||||
|
||||
3. 联系作者 <a href="mailto:pig4cloud@qq.com">pig4cloud@qq.com</a>
|
@ -1,6 +1,6 @@
|
||||
version: '3'
|
||||
services:
|
||||
pigx-ui:
|
||||
pig-ui:
|
||||
build:
|
||||
context: .
|
||||
restart: always
|
||||
|
@ -15,7 +15,7 @@ server {
|
||||
root /data/;
|
||||
|
||||
location ^~/api/ {
|
||||
proxy_pass http://pigx-gateway:9999/; #注意/后缀
|
||||
proxy_pass http://pig-gateway:9999/; #注意/后缀
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
|
@ -18,7 +18,7 @@
|
||||
<!--避免微信管理防盗链机制-->
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>PIGX 微服务快速开发平台</title>
|
||||
<title>PIG 微服务快速开发平台</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -26,7 +26,6 @@
|
||||
"echarts": "^5.4.1",
|
||||
"vue-echarts": "^6.2.3",
|
||||
"element-plus": "2.3.1",
|
||||
"form-designer-plus": "^0.1.5",
|
||||
"highlight.js": "^11.7.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"mitt": "^3.0.0",
|
||||
|
@ -1,24 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/audit/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function getObj(id?: string) {
|
||||
return request({
|
||||
url: '/admin/audit/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function delObjs(ids?: Object) {
|
||||
return request({
|
||||
url: '/admin/audit',
|
||||
method: 'delete',
|
||||
data: ids,
|
||||
});
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export const fetchList = (query?: Object) => {
|
||||
return request({
|
||||
url: '/admin/route',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
};
|
||||
|
||||
export const putObj = (obj?: object) => {
|
||||
return request({
|
||||
url: '/admin/route',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
};
|
||||
|
||||
export const refreshObj = () => {
|
||||
return request({
|
||||
url: '/actuator/gateway/refresh',
|
||||
method: 'post',
|
||||
});
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/schedule/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function list(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/schedule/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function addObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/schedule',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function getObj(id?: string) {
|
||||
return request({
|
||||
url: '/admin/schedule/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function delObjs(ids?: Object) {
|
||||
return request({
|
||||
url: '/admin/schedule',
|
||||
method: 'delete',
|
||||
data: ids,
|
||||
});
|
||||
}
|
||||
|
||||
export function putObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/schedule',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/social/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function addObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/social',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function getObj(id?: string) {
|
||||
return request({
|
||||
url: '/admin/social/getById/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function delObj(ids?: Object) {
|
||||
return request({
|
||||
url: '/admin/social',
|
||||
method: 'delete',
|
||||
data: ids,
|
||||
});
|
||||
}
|
||||
|
||||
export function putObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/social',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant-menu/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function addObj(obj: object) {
|
||||
return request({
|
||||
url: '/admin/tenant-menu',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function getObj(id: string) {
|
||||
return request({
|
||||
url: '/admin/tenant-menu/',
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function delObj(id: string) {
|
||||
return request({
|
||||
url: '/admin/tenant-menu/' + id,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
export function putObj(obj: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant-menu',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function menuList() {
|
||||
return request({
|
||||
url: '/admin/tenant-menu/list',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function treemenu() {
|
||||
return request({
|
||||
url: '/admin/tenant-menu/tree/menu',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
export function fetchPage(query?: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant/page',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchList(query?: object) {
|
||||
return request({
|
||||
url: '/admin/tenant/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function addObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function getObj(id?: string) {
|
||||
return request({
|
||||
url: '/admin/tenant/details/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function getObjDetails(obj?: object) {
|
||||
return request({
|
||||
url: '/admin/tenant/details',
|
||||
method: 'get',
|
||||
params: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function delObj(ids?: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant',
|
||||
method: 'delete',
|
||||
data: ids,
|
||||
});
|
||||
}
|
||||
|
||||
export function putObj(obj?: Object) {
|
||||
return request({
|
||||
url: '/admin/tenant',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function validateTenantName(rule: any, value: any, callback: any, isEdit: boolean) {
|
||||
if (isEdit) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
getObjDetails({ name: value }).then((response) => {
|
||||
const result = response.data;
|
||||
if (result !== null) {
|
||||
callback(new Error('租户名称已经存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function validateTenantCode(rule: any, value: any, callback: any, isEdit: boolean) {
|
||||
if (isEdit) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
getObjDetails({ code: value }).then((response) => {
|
||||
const result = response.data;
|
||||
if (result !== null) {
|
||||
callback(new Error('租户编码已经存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import request from '/@/utils/request';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { validateNull } from '/@/utils/validate';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {validateNull} from '/@/utils/validate';
|
||||
import {useUserInfo} from '/@/stores/userInfo';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
/**
|
||||
@ -15,83 +15,83 @@ const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';
|
||||
* @param data
|
||||
*/
|
||||
export const login = (data: any) => {
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_PASSWORD_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
// 密码加密
|
||||
const encPassword = other.encryption(data.password, import.meta.env.VITE_PWD_ENC_KEY);
|
||||
const { username, randomStr, code, grant_type, scope } = data;
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
method: 'post',
|
||||
params: { username, randomStr, code, grant_type, scope },
|
||||
data: { password: encPassword },
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
});
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_PASSWORD_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
// 密码加密
|
||||
const encPassword = other.encryption(data.password, import.meta.env.VITE_PWD_ENC_KEY);
|
||||
const {username, randomStr, code, grant_type, scope} = data;
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
method: 'post',
|
||||
params: {username, randomStr, code, grant_type, scope},
|
||||
data: {password: encPassword},
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const loginByMobile = (mobile: any, code: any) => {
|
||||
const grant_type = 'mobile';
|
||||
const scope = 'server';
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_MOBILE_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
const grant_type = 'mobile';
|
||||
const scope = 'server';
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_MOBILE_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: { mobile: 'SMS@' + mobile, code: code, grant_type, scope },
|
||||
});
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: {mobile: mobile, code: code, grant_type, scope},
|
||||
});
|
||||
};
|
||||
|
||||
export const loginBySocial = (state: string, code: string) => {
|
||||
const grant_type = 'mobile';
|
||||
const scope = 'server';
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_SOCIAL_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
const grant_type = 'mobile';
|
||||
const scope = 'server';
|
||||
const basicAuth = 'Basic ' + window.btoa(import.meta.env.VITE_OAUTH2_SOCIAL_CLIENT);
|
||||
Session.set('basicAuth', basicAuth);
|
||||
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: { mobile: state + '@' + code, code: code, grant_type, scope },
|
||||
});
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: {mobile: state + '@' + code, code: code, grant_type, scope},
|
||||
});
|
||||
};
|
||||
|
||||
export const sendMobileCode = (mobile: any) => {
|
||||
return request({
|
||||
url: '/admin/mobile/' + mobile,
|
||||
method: 'get',
|
||||
});
|
||||
return request({
|
||||
url: '/admin/mobile/' + mobile,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const refreshTokenApi = (refresh_token: string) => {
|
||||
const grant_type = 'refresh_token';
|
||||
const scope = 'server';
|
||||
// 获取当前选中的 basic 认证信息
|
||||
const basicAuth = Session.get('basicAuth');
|
||||
const grant_type = 'refresh_token';
|
||||
const scope = 'server';
|
||||
// 获取当前选中的 basic 认证信息
|
||||
const basicAuth = Session.get('basicAuth');
|
||||
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: { refresh_token, grant_type, scope },
|
||||
});
|
||||
return request({
|
||||
url: '/auth/oauth2/token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'post',
|
||||
params: {refresh_token, grant_type, scope},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -99,58 +99,58 @@ export const refreshTokenApi = (refresh_token: string) => {
|
||||
* @param refreshLock
|
||||
*/
|
||||
export const checkToken = (refreshTime: number, refreshLock: boolean) => {
|
||||
const basicAuth = Session.get('basicAuth');
|
||||
request({
|
||||
url: '/auth/token/check_token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'get',
|
||||
params: { token: Session.getToken() },
|
||||
})
|
||||
.then((response) => {
|
||||
if (validateNull(response) || response.code === 1) {
|
||||
clearInterval(refreshTime);
|
||||
return;
|
||||
}
|
||||
const expire = Date.parse(response.data.expiresAt);
|
||||
if (expire) {
|
||||
const expiredPeriod = expire - new Date().getTime();
|
||||
//小于半小时自动续约
|
||||
if (expiredPeriod <= 30 * 60 * 1000) {
|
||||
if (!refreshLock) {
|
||||
refreshLock = true;
|
||||
useUserInfo()
|
||||
.refreshToken()
|
||||
.catch(() => {
|
||||
clearInterval(refreshTime);
|
||||
});
|
||||
refreshLock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 发生异常关闭定时器
|
||||
clearInterval(refreshTime);
|
||||
});
|
||||
const basicAuth = Session.get('basicAuth');
|
||||
request({
|
||||
url: '/auth/token/check_token',
|
||||
headers: {
|
||||
skipToken: true,
|
||||
Authorization: basicAuth,
|
||||
'Content-Type': FORM_CONTENT_TYPE,
|
||||
},
|
||||
method: 'get',
|
||||
params: {token: Session.getToken()},
|
||||
})
|
||||
.then((response) => {
|
||||
if (validateNull(response) || response.code === 1) {
|
||||
clearInterval(refreshTime);
|
||||
return;
|
||||
}
|
||||
const expire = Date.parse(response.data.expiresAt);
|
||||
if (expire) {
|
||||
const expiredPeriod = expire - new Date().getTime();
|
||||
//小于半小时自动续约
|
||||
if (expiredPeriod <= 30 * 60 * 1000) {
|
||||
if (!refreshLock) {
|
||||
refreshLock = true;
|
||||
useUserInfo()
|
||||
.refreshToken()
|
||||
.catch(() => {
|
||||
clearInterval(refreshTime);
|
||||
});
|
||||
refreshLock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 发生异常关闭定时器
|
||||
clearInterval(refreshTime);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export const getUserInfo = () => {
|
||||
return request({
|
||||
url: '/admin/user/info',
|
||||
method: 'get',
|
||||
});
|
||||
return request({
|
||||
url: '/admin/user/info',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const logout = () => {
|
||||
return request({
|
||||
url: '/auth/token/logout',
|
||||
method: 'delete',
|
||||
});
|
||||
return request({
|
||||
url: '/auth/token/logout',
|
||||
method: 'delete',
|
||||
});
|
||||
};
|
||||
|
@ -11,11 +11,11 @@
|
||||
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="!v.meta.tagsViewName ? v.name : v.meta.tagsViewName">
|
||||
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<div v-if="!v.meta.tagsViewName">{{ $t(v.name) }}</div>
|
||||
<div v-if="!v.meta.tagsViewName">{{ other.setMenuI18n(v) }}</div>
|
||||
<div v-else>{{ v.meta.tagsViewName }}</div>
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.name) }}
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ other.setMenuI18n(v) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
@ -96,7 +96,7 @@ const initRouteSplit = (path: string) => {
|
||||
if (route.name === 'router.home' || (route.name === 'staticRoutes.notFound' && state.breadcrumbList.length > 1)) {
|
||||
state.breadcrumbList.splice(0, state.breadcrumbList.length - 1);
|
||||
} else if (state.breadcrumbList.length > 0) {
|
||||
state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(<RouteToFrom>route);
|
||||
state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setMenuI18n(<RouteToFrom>route);
|
||||
}
|
||||
};
|
||||
// 页面加载时
|
||||
|
@ -97,7 +97,7 @@ const getThemeConfig = computed(() => {
|
||||
// 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
const setTagsViewNameI18n = computed(() => {
|
||||
return (v: RouteItem) => {
|
||||
return other.setTagsViewNameI18n(v);
|
||||
return other.setMenuI18n(v);
|
||||
};
|
||||
});
|
||||
// 设置 tagsView 高亮
|
||||
|
@ -6,7 +6,7 @@
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ other.setMenuI18n(val) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
@ -14,12 +14,12 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.name) }}
|
||||
{{ other.setMenuI18n(val) }}
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.name) }}
|
||||
{{ other.setMenuI18n(val) }}
|
||||
</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ other.setMenuI18n(val) }}</span>
|
||||
</template>
|
||||
<sub-item :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
@ -11,12 +11,12 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ other.setMenuI18n(val) }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
{{ $t(val.name) }}
|
||||
{{ other.setMenuI18n(val) }}
|
||||
</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<span>{{ $t(val.name) }}</span>
|
||||
<span>{{ other.setMenuI18n(val) }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
@ -19,10 +19,10 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<span>{{ $t(val.name) }} </span>
|
||||
<span>{{ other.setMenuI18n(val) }} </span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.name) }}</a>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ other.setMenuI18n(val) }}</a>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
@ -9,8 +9,6 @@ import '/@/theme/index.scss';
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/dist/index.css';
|
||||
import VForm3 from 'form-designer-plus'; //引入VForm3库
|
||||
import 'form-designer-plus/dist/designer.style.css'; //引入VForm3样式
|
||||
|
||||
import { ElementIcons, Pagination, RightToolbar, DictTag, UploadExcel, UploadFile, UploadImg, Editor, Tip, DelWrap } from '/@/components/index';
|
||||
import { parseTime, parseDate, dateTimeStr, dateStr, timeStr } from '/@/utils/formatTime';
|
||||
@ -52,7 +50,6 @@ app
|
||||
.use(router) // 路由
|
||||
.use(ElementPlus, { i18n: i18n.global.t }) // ElementPlus 全局引入
|
||||
.use(ElementIcons) // elementIcons 图标全局引入
|
||||
.use(VForm3) // 表单设计
|
||||
.use(i18n) // 国际化
|
||||
.use(hljsVuePlugin) // 代码高亮
|
||||
.mount('#app');
|
||||
|
@ -101,7 +101,7 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
// 是否开启水印
|
||||
isWartermark: true,
|
||||
// 水印文案
|
||||
wartermarkText: 'PigX',
|
||||
wartermarkText: 'Pig',
|
||||
|
||||
/**
|
||||
* 其它设置
|
||||
@ -133,9 +133,9 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 全局网站标题 / 副标题
|
||||
*/
|
||||
// 网站主标题(菜单导航、浏览器当前网页标题、登录form顶部右侧)
|
||||
globalTitle: 'PIGX ADMIN',
|
||||
globalTitle: 'PIG ADMIN',
|
||||
// 网站副标题(登录左侧底部页顶部文字)
|
||||
globalViceTitle: 'PigX 快速开发框架',
|
||||
globalViceTitle: 'Pig 快速开发框架',
|
||||
// 网站副标题(登录页顶部文字)
|
||||
globalViceTitleMsg: '专注、免费、开源、维护、解疑',
|
||||
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
|
||||
|
@ -223,7 +223,7 @@ body,
|
||||
}
|
||||
.flex-center {
|
||||
@extend .flex;
|
||||
flex-direction: column;
|
||||
flex-direction: column !important;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { nextTick, defineAsyncComponent } from 'vue';
|
||||
import type { App } from 'vue';
|
||||
import {nextTick, defineAsyncComponent} from 'vue';
|
||||
import type {App} from 'vue';
|
||||
import * as svg from '@element-plus/icons-vue';
|
||||
import router from '/@/router/index';
|
||||
import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { i18n } from '/@/i18n/index';
|
||||
import { Local } from '/@/utils/storage';
|
||||
import { verifyUrl } from '/@/utils/toolsValidate';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {useThemeConfig} from '/@/stores/themeConfig';
|
||||
import {i18n} from '/@/i18n/index';
|
||||
import {Local} from '/@/utils/storage';
|
||||
import {verifyUrl} from '/@/utils/toolsValidate';
|
||||
import request from '/@/utils/request';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
// @ts-ignore
|
||||
import * as CryptoJS from 'crypto-js';
|
||||
import { validateNull } from './validate';
|
||||
import {validateNull} from './validate';
|
||||
import {RouteItem, RouteItems, RouteToFrom} from "/@/types/global";
|
||||
|
||||
// 引入组件
|
||||
const SvgIcon = defineAsyncComponent(() => import('/@/components/SvgIcon/index.vue'));
|
||||
@ -23,11 +24,11 @@ const SvgIcon = defineAsyncComponent(() => import('/@/components/SvgIcon/index.v
|
||||
* @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
|
||||
*/
|
||||
export function elSvg(app: App) {
|
||||
const icons = svg as any;
|
||||
for (const i in icons) {
|
||||
app.component(`ele-${icons[i].name}`, icons[i]);
|
||||
}
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
const icons = svg as any;
|
||||
for (const i in icons) {
|
||||
app.component(`ele-${icons[i].name}`, icons[i]);
|
||||
}
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,41 +36,32 @@ export function elSvg(app: App) {
|
||||
* @method const title = useTitle(); ==> title()
|
||||
*/
|
||||
export function useTitle() {
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
nextTick(() => {
|
||||
let globalTitle: string = themeConfig.value.globalTitle;
|
||||
let webTitle = setTagsViewNameI18n(router.currentRoute.value);
|
||||
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
|
||||
});
|
||||
const stores = useThemeConfig(pinia);
|
||||
const {themeConfig} = storeToRefs(stores);
|
||||
nextTick(() => {
|
||||
let globalTitle: string = themeConfig.value.globalTitle;
|
||||
let webTitle = setMenuI18n(router.currentRoute.value);
|
||||
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 自定义 tagsView 名称、 自定义 tagsView 名称国际化
|
||||
* @param params 路由 query、params 中的 tagsViewName
|
||||
* @returns 返回当前 tagsViewName 名称
|
||||
* 设置菜单国际化
|
||||
* @param {object} item - 菜单项对象
|
||||
* @param {string} item.enName - 英文名称
|
||||
* @param {string} item.name - 名称
|
||||
* @returns {string} - 国际化后的名称
|
||||
*/
|
||||
export function setTagsViewNameI18n(item: any) {
|
||||
let tagsViewName: string = '';
|
||||
const { query, params } = item;
|
||||
//修复tagsViewName匹配到其他含下列单词的路由
|
||||
const pattern = /^\{("(zh-cn|en|zh-tw)":"[^,]+",?){1,3}}$/;
|
||||
if (query?.tagsViewName || params?.tagsViewName) {
|
||||
if (pattern.test(query?.tagsViewName) || pattern.test(params?.tagsViewName)) {
|
||||
// 国际化
|
||||
const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
|
||||
tagsViewName = urlTagsParams[i18n.global.locale.value];
|
||||
} else {
|
||||
// 非国际化
|
||||
tagsViewName = query?.tagsViewName || params?.tagsViewName;
|
||||
}
|
||||
} else {
|
||||
// 非自定义 tagsView 名称
|
||||
tagsViewName = i18n.global.t(item.name);
|
||||
}
|
||||
return tagsViewName;
|
||||
export function setMenuI18n(item: any) {
|
||||
let name = i18n.global.t(item.name)
|
||||
if (name !== item.name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return i18n.global.locale.value === 'en' ? item.meta.enName : item.meta.title;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 图片懒加载
|
||||
* @param el dom 目标元素
|
||||
@ -77,21 +69,21 @@ export function setTagsViewNameI18n(item: any) {
|
||||
* @description data-xxx 属性用于存储页面或应用程序的私有自定义数据
|
||||
*/
|
||||
export const lazyImg = (el: string, arr: EmptyArrayType) => {
|
||||
const io = new IntersectionObserver((res) => {
|
||||
res.forEach((v: any) => {
|
||||
if (v.isIntersecting) {
|
||||
const { img, key } = v.target.dataset;
|
||||
v.target.src = img;
|
||||
v.target.onload = () => {
|
||||
io.unobserve(v.target);
|
||||
arr[key]['loading'] = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
nextTick(() => {
|
||||
document.querySelectorAll(el).forEach((img) => io.observe(img));
|
||||
});
|
||||
const io = new IntersectionObserver((res) => {
|
||||
res.forEach((v: any) => {
|
||||
if (v.isIntersecting) {
|
||||
const {img, key} = v.target.dataset;
|
||||
v.target.src = img;
|
||||
v.target.onload = () => {
|
||||
io.unobserve(v.target);
|
||||
arr[key]['loading'] = false;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
nextTick(() => {
|
||||
document.querySelectorAll(el).forEach((img) => io.observe(img));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -99,9 +91,9 @@ export const lazyImg = (el: string, arr: EmptyArrayType) => {
|
||||
* @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
|
||||
*/
|
||||
export const globalComponentSize = (): string => {
|
||||
const stores = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(stores);
|
||||
return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize;
|
||||
const stores = useThemeConfig(pinia);
|
||||
const {themeConfig} = storeToRefs(stores);
|
||||
return Local.get('themeConfig')?.globalComponentSize || themeConfig.value?.globalComponentSize;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -110,35 +102,35 @@ export const globalComponentSize = (): string => {
|
||||
* @returns 克隆后的对象
|
||||
*/
|
||||
export function deepClone(obj: EmptyObjectType) {
|
||||
let newObj: EmptyObjectType;
|
||||
try {
|
||||
newObj = obj.push ? [] : {};
|
||||
} catch (error) {
|
||||
newObj = {};
|
||||
}
|
||||
for (let attr in obj) {
|
||||
if (obj[attr] && typeof obj[attr] === 'object') {
|
||||
newObj[attr] = deepClone(obj[attr]);
|
||||
} else {
|
||||
newObj[attr] = obj[attr];
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
let newObj: EmptyObjectType;
|
||||
try {
|
||||
newObj = obj.push ? [] : {};
|
||||
} catch (error) {
|
||||
newObj = {};
|
||||
}
|
||||
for (let attr in obj) {
|
||||
if (obj[attr] && typeof obj[attr] === 'object') {
|
||||
newObj[attr] = deepClone(obj[attr]);
|
||||
} else {
|
||||
newObj[attr] = obj[attr];
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是移动端
|
||||
*/
|
||||
export function isMobile() {
|
||||
if (
|
||||
navigator.userAgent.match(
|
||||
/('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
navigator.userAgent.match(
|
||||
/('phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone')/i
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,18 +140,18 @@ export function isMobile() {
|
||||
* @returns 删除空值后的数组对象
|
||||
*/
|
||||
export function handleEmpty(list: EmptyArrayType) {
|
||||
const arr = [] as any[];
|
||||
for (const i in list) {
|
||||
const d = [] as any[];
|
||||
for (const j in list[i]) {
|
||||
d.push(list[i][j]);
|
||||
}
|
||||
const leng = d.filter((item) => item === '').length;
|
||||
if (leng !== d.length) {
|
||||
arr.push(list[i]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
const arr = [] as any[];
|
||||
for (const i in list) {
|
||||
const d = [] as any[];
|
||||
for (const j in list[i]) {
|
||||
d.push(list[i][j]);
|
||||
}
|
||||
const leng = d.filter((item) => item === '').length;
|
||||
if (leng !== d.length) {
|
||||
arr.push(list[i]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,54 +159,55 @@ export function handleEmpty(list: EmptyArrayType) {
|
||||
* @param val 当前点击项菜单
|
||||
*/
|
||||
export function handleOpenLink(val: RouteItem) {
|
||||
router.push(val.path);
|
||||
if (verifyUrl(<string>val.meta?.isLink)) window.open(val.meta?.isLink);
|
||||
else window.open(`${val.meta?.isLink}`);
|
||||
router.push(val.path);
|
||||
if (verifyUrl(<string>val.meta?.isLink)) window.open(val.meta?.isLink);
|
||||
else window.open(`${val.meta?.isLink}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开小窗口
|
||||
*/
|
||||
export const openWindow = (url: string, title: string, w: number, h: number) => {
|
||||
// @ts-ignore
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
||||
// @ts-ignore
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
||||
// @ts-ignore
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
||||
// @ts-ignore
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
||||
|
||||
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
||||
const height = window.innerHeight
|
||||
? window.innerHeight
|
||||
: document.documentElement.clientHeight
|
||||
? document.documentElement.clientHeight
|
||||
: screen.height;
|
||||
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
||||
const height = window.innerHeight
|
||||
? window.innerHeight
|
||||
: document.documentElement.clientHeight
|
||||
? document.documentElement.clientHeight
|
||||
: screen.height;
|
||||
|
||||
const left = width / 2 - w / 2 + dualScreenLeft;
|
||||
const top = height / 2 - h / 2 + dualScreenTop;
|
||||
return window.open(
|
||||
url,
|
||||
title,
|
||||
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' +
|
||||
w +
|
||||
', height=' +
|
||||
h +
|
||||
', top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left
|
||||
);
|
||||
const left = width / 2 - w / 2 + dualScreenLeft;
|
||||
const top = height / 2 - h / 2 + dualScreenTop;
|
||||
return window.open(
|
||||
url,
|
||||
title,
|
||||
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' +
|
||||
w +
|
||||
', height=' +
|
||||
h +
|
||||
', top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*加密处理
|
||||
*/
|
||||
export function encryption(src: string, keyWord: string) {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
// 加密
|
||||
var encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.CFB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
return encrypted.toString();
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
// 加密
|
||||
var encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.CFB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,15 +216,15 @@ export function encryption(src: string, keyWord: string) {
|
||||
* @returns 明文
|
||||
*/
|
||||
export function decryption(src: string, keyWord: string) {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
// 解密逻辑
|
||||
var decryptd = CryptoJS.AES.decrypt(src, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.CFB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
// 解密逻辑
|
||||
var decryptd = CryptoJS.AES.decrypt(src, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.CFB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
|
||||
return decryptd.toString(CryptoJS.enc.Utf8);
|
||||
return decryptd.toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,8 +233,8 @@ export function decryption(src: string, keyWord: string) {
|
||||
* @returns 密文
|
||||
*/
|
||||
export function base64Encrypt(src: string) {
|
||||
const encodedWord = CryptoJS.enc.Utf8.parse(src);
|
||||
return CryptoJS.enc.Base64.stringify(encodedWord);
|
||||
const encodedWord = CryptoJS.enc.Utf8.parse(src);
|
||||
return CryptoJS.enc.Base64.stringify(encodedWord);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,14 +245,14 @@ export function base64Encrypt(src: string) {
|
||||
* @returns {*}
|
||||
*/
|
||||
export function downBlobFile(url: any, query: any, fileName: string) {
|
||||
return request({
|
||||
url: url,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
params: query,
|
||||
}).then((response) => {
|
||||
handleBlobFile(response, fileName);
|
||||
});
|
||||
return request({
|
||||
url: url,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
params: query,
|
||||
}).then((response) => {
|
||||
handleBlobFile(response, fileName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,26 +261,26 @@ export function downBlobFile(url: any, query: any, fileName: string) {
|
||||
* @returns
|
||||
*/
|
||||
export function handleBlobFile(response: any, fileName: string) {
|
||||
// 处理返回的文件流
|
||||
const blob = response;
|
||||
if (blob && blob.size === 0) {
|
||||
useMessage().error('内容为空,无法下载');
|
||||
return;
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
// 处理返回的文件流
|
||||
const blob = response;
|
||||
if (blob && blob.size === 0) {
|
||||
useMessage().error('内容为空,无法下载');
|
||||
return;
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
|
||||
// 兼容一下 入参不是 File Blob 类型情况
|
||||
var binaryData = [] as any;
|
||||
binaryData.push(response);
|
||||
link.href = window.URL.createObjectURL(new Blob(binaryData));
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
// @ts-ignore
|
||||
URL.revokeObjectURL(blob);
|
||||
document.body.removeChild(link);
|
||||
}, 0);
|
||||
// 兼容一下 入参不是 File Blob 类型情况
|
||||
var binaryData = [] as any;
|
||||
binaryData.push(response);
|
||||
link.href = window.URL.createObjectURL(new Blob(binaryData));
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
// @ts-ignore
|
||||
URL.revokeObjectURL(blob);
|
||||
document.body.removeChild(link);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,32 +288,33 @@ export function handleBlobFile(response: any, fileName: string) {
|
||||
* @return string
|
||||
*/
|
||||
export function generateUUID() {
|
||||
if (typeof crypto === 'object') {
|
||||
if (typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
|
||||
const callback = (c: any) => {
|
||||
const num = Number(c);
|
||||
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
|
||||
};
|
||||
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback);
|
||||
}
|
||||
}
|
||||
let timestamp = new Date().getTime();
|
||||
let performanceNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
let random = Math.random() * 16;
|
||||
if (timestamp > 0) {
|
||||
random = (timestamp + random) % 16 | 0;
|
||||
timestamp = Math.floor(timestamp / 16);
|
||||
} else {
|
||||
random = (performanceNow + random) % 16 | 0;
|
||||
performanceNow = Math.floor(performanceNow / 16);
|
||||
}
|
||||
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
|
||||
});
|
||||
if (typeof crypto === 'object') {
|
||||
if (typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
|
||||
const callback = (c: any) => {
|
||||
const num = Number(c);
|
||||
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
|
||||
};
|
||||
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback);
|
||||
}
|
||||
}
|
||||
let timestamp = new Date().getTime();
|
||||
let performanceNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
let random = Math.random() * 16;
|
||||
if (timestamp > 0) {
|
||||
random = (timestamp + random) % 16 | 0;
|
||||
timestamp = Math.floor(timestamp / 16);
|
||||
} else {
|
||||
random = (performanceNow + random) % 16 | 0;
|
||||
performanceNow = Math.floor(performanceNow / 16);
|
||||
}
|
||||
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一批量导出
|
||||
* @method elSvg 导出全局注册 element plus svg 图标
|
||||
@ -334,85 +328,85 @@ export function generateUUID() {
|
||||
* @method handleOpenLink 打开外部链接
|
||||
*/
|
||||
const other = {
|
||||
elSvg: (app: App) => {
|
||||
elSvg(app);
|
||||
},
|
||||
useTitle: () => {
|
||||
useTitle();
|
||||
},
|
||||
setTagsViewNameI18n(route: RouteToFrom) {
|
||||
return setTagsViewNameI18n(route);
|
||||
},
|
||||
lazyImg: (el: string, arr: EmptyArrayType) => {
|
||||
lazyImg(el, arr);
|
||||
},
|
||||
globalComponentSize: () => {
|
||||
return globalComponentSize();
|
||||
},
|
||||
deepClone: (obj: EmptyObjectType) => {
|
||||
return deepClone(obj);
|
||||
},
|
||||
isMobile: () => {
|
||||
return isMobile();
|
||||
},
|
||||
handleEmpty: (list: EmptyArrayType) => {
|
||||
return handleEmpty(list);
|
||||
},
|
||||
handleOpenLink: (val: RouteItem) => {
|
||||
handleOpenLink(val);
|
||||
},
|
||||
encryption: (src: string, keyWord: string) => {
|
||||
return encryption(src, keyWord);
|
||||
},
|
||||
decryption: (src: string, keyWord: string) => {
|
||||
return decryption(src, keyWord);
|
||||
},
|
||||
base64Encrypt: (data: any) => {
|
||||
return base64Encrypt(data);
|
||||
},
|
||||
downBlobFile: (url: any, query: any, fileName: string) => {
|
||||
return downBlobFile(url, query, fileName);
|
||||
},
|
||||
toUnderline: (str: string) => {
|
||||
return toUnderline(str);
|
||||
},
|
||||
openWindow: (url: string, title: string, w: number, h: number) => {
|
||||
return openWindow(url, title, w, h);
|
||||
},
|
||||
getQueryString: (url: string, paraName: string) => {
|
||||
return getQueryString(url, paraName);
|
||||
},
|
||||
adaptationUrl: (url?: string) => {
|
||||
return adaptationUrl(url);
|
||||
},
|
||||
resolveAllEunuchNodeId: (json: any[], idArr: any[], temp: any[] = []) => {
|
||||
return resolveAllEunuchNodeId(json, idArr, temp);
|
||||
},
|
||||
getNonDuplicateID: () => {
|
||||
return getNonDuplicateID();
|
||||
},
|
||||
elSvg: (app: App) => {
|
||||
elSvg(app);
|
||||
},
|
||||
useTitle: () => {
|
||||
useTitle();
|
||||
},
|
||||
setMenuI18n(item: RouteItems) {
|
||||
return setMenuI18n(item)
|
||||
},
|
||||
lazyImg: (el: string, arr: EmptyArrayType) => {
|
||||
lazyImg(el, arr);
|
||||
},
|
||||
globalComponentSize: () => {
|
||||
return globalComponentSize();
|
||||
},
|
||||
deepClone: (obj: EmptyObjectType) => {
|
||||
return deepClone(obj);
|
||||
},
|
||||
isMobile: () => {
|
||||
return isMobile();
|
||||
},
|
||||
handleEmpty: (list: EmptyArrayType) => {
|
||||
return handleEmpty(list);
|
||||
},
|
||||
handleOpenLink: (val: RouteItem) => {
|
||||
handleOpenLink(val);
|
||||
},
|
||||
encryption: (src: string, keyWord: string) => {
|
||||
return encryption(src, keyWord);
|
||||
},
|
||||
decryption: (src: string, keyWord: string) => {
|
||||
return decryption(src, keyWord);
|
||||
},
|
||||
base64Encrypt: (data: any) => {
|
||||
return base64Encrypt(data);
|
||||
},
|
||||
downBlobFile: (url: any, query: any, fileName: string) => {
|
||||
return downBlobFile(url, query, fileName);
|
||||
},
|
||||
toUnderline: (str: string) => {
|
||||
return toUnderline(str);
|
||||
},
|
||||
openWindow: (url: string, title: string, w: number, h: number) => {
|
||||
return openWindow(url, title, w, h);
|
||||
},
|
||||
getQueryString: (url: string, paraName: string) => {
|
||||
return getQueryString(url, paraName);
|
||||
},
|
||||
adaptationUrl: (url?: string) => {
|
||||
return adaptationUrl(url);
|
||||
},
|
||||
resolveAllEunuchNodeId: (json: any[], idArr: any[], temp: any[] = []) => {
|
||||
return resolveAllEunuchNodeId(json, idArr, temp);
|
||||
},
|
||||
getNonDuplicateID: () => {
|
||||
return getNonDuplicateID();
|
||||
},
|
||||
|
||||
addUnit: (value: string | number, unit = 'px') => {
|
||||
return addUnit(value, unit);
|
||||
},
|
||||
addUnit: (value: string | number, unit = 'px') => {
|
||||
return addUnit(value, unit);
|
||||
},
|
||||
};
|
||||
|
||||
export function getQueryString(url: string, paraName: string) {
|
||||
const arrObj = url.split('?');
|
||||
if (arrObj.length > 1) {
|
||||
const arrPara = arrObj[1].split('&');
|
||||
let arr;
|
||||
for (let i = 0; i < arrPara.length; i++) {
|
||||
arr = arrPara[i].split('=');
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (arr != null && arr[0] == paraName) {
|
||||
return arr[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
const arrObj = url.split('?');
|
||||
if (arrObj.length > 1) {
|
||||
const arrPara = arrObj[1].split('&');
|
||||
let arr;
|
||||
for (let i = 0; i < arrPara.length; i++) {
|
||||
arr = arrPara[i].split('=');
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (arr != null && arr[0] == paraName) {
|
||||
return arr[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -425,31 +419,31 @@ export function getQueryString(url: string, paraName: string) {
|
||||
* @returns {*}
|
||||
*/
|
||||
export function handleTree(data: any, id: any, parentId: any, children: any, rootId: any) {
|
||||
id = id || 'id';
|
||||
parentId = parentId || 'parentId';
|
||||
children = children || 'children';
|
||||
rootId =
|
||||
rootId ||
|
||||
Math.min.apply(
|
||||
Math,
|
||||
data.map((item: any) => {
|
||||
return item[parentId];
|
||||
})
|
||||
) ||
|
||||
0;
|
||||
//对源数据深度克隆
|
||||
const cloneData = JSON.parse(JSON.stringify(data));
|
||||
//循环所有项
|
||||
const treeData = cloneData.filter((father: any) => {
|
||||
const branchArr = cloneData.filter((child: any) => {
|
||||
//返回每一项的子级数组
|
||||
return father[id] === child[parentId];
|
||||
});
|
||||
branchArr.length > 0 ? (father[children] = branchArr) : '';
|
||||
//返回第一层
|
||||
return father[parentId] === rootId;
|
||||
});
|
||||
return treeData !== '' ? treeData : data;
|
||||
id = id || 'id';
|
||||
parentId = parentId || 'parentId';
|
||||
children = children || 'children';
|
||||
rootId =
|
||||
rootId ||
|
||||
Math.min.apply(
|
||||
Math,
|
||||
data.map((item: any) => {
|
||||
return item[parentId];
|
||||
})
|
||||
) ||
|
||||
0;
|
||||
//对源数据深度克隆
|
||||
const cloneData = JSON.parse(JSON.stringify(data));
|
||||
//循环所有项
|
||||
const treeData = cloneData.filter((father: any) => {
|
||||
const branchArr = cloneData.filter((child: any) => {
|
||||
//返回每一项的子级数组
|
||||
return father[id] === child[parentId];
|
||||
});
|
||||
branchArr.length > 0 ? (father[children] = branchArr) : '';
|
||||
//返回第一层
|
||||
return father[parentId] === rootId;
|
||||
});
|
||||
return treeData !== '' ? treeData : data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -457,14 +451,14 @@ export function handleTree(data: any, id: any, parentId: any, children: any, roo
|
||||
* @returns
|
||||
*/
|
||||
const resolveAllEunuchNodeId = (json: any[], idArr: any[], temp: any[] = []) => {
|
||||
for (const item of json) {
|
||||
if (item.children && item.children.length !== 0) {
|
||||
resolveAllEunuchNodeId(item.children, idArr, temp);
|
||||
} else {
|
||||
temp.push(...idArr.filter((id) => id === item.id));
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
for (const item of json) {
|
||||
if (item.children && item.children.length !== 0) {
|
||||
resolveAllEunuchNodeId(item.children, idArr, temp);
|
||||
} else {
|
||||
temp.push(...idArr.filter((id) => id === item.id));
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -473,7 +467,7 @@ const resolveAllEunuchNodeId = (json: any[], idArr: any[], temp: any[] = []) =>
|
||||
* @returns 下划线
|
||||
*/
|
||||
export function toUnderline(str: string) {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -484,23 +478,23 @@ export function toUnderline(str: string) {
|
||||
* @param originUrl 原始路径
|
||||
*/
|
||||
const adaptationUrl = (originUrl?: string) => {
|
||||
// 微服务架构 不做路径转换,为空不做路径转换
|
||||
const isMicro = import.meta.env.VITE_IS_MICRO;
|
||||
if (validateNull(isMicro) || isMicro === 'true') {
|
||||
return originUrl;
|
||||
}
|
||||
// 微服务架构 不做路径转换,为空不做路径转换
|
||||
const isMicro = import.meta.env.VITE_IS_MICRO;
|
||||
if (validateNull(isMicro) || isMicro === 'true') {
|
||||
return originUrl;
|
||||
}
|
||||
|
||||
// 验证码服务
|
||||
if (originUrl?.startsWith('/code/')) {
|
||||
return `/admin${originUrl}`;
|
||||
}
|
||||
// 验证码服务
|
||||
if (originUrl?.startsWith('/code/')) {
|
||||
return `/admin${originUrl}`;
|
||||
}
|
||||
|
||||
// 如果是代码生成服务,不做路径转换
|
||||
if (originUrl?.startsWith('/gen')) {
|
||||
return originUrl;
|
||||
}
|
||||
// 转为 /admin 路由前缀的请求
|
||||
return `/admin/${originUrl?.split('/').splice(2).join('/')}`;
|
||||
// 如果是代码生成服务,不做路径转换
|
||||
if (originUrl?.startsWith('/gen')) {
|
||||
return originUrl;
|
||||
}
|
||||
// 转为 /admin 路由前缀的请求
|
||||
return `/admin/${originUrl?.split('/').splice(2).join('/')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -509,9 +503,9 @@ const adaptationUrl = (originUrl?: string) => {
|
||||
* @return { String } id
|
||||
*/
|
||||
const getNonDuplicateID = (length = 8) => {
|
||||
let idStr = Date.now().toString(36);
|
||||
idStr += Math.random().toString(36).substring(3, length);
|
||||
return idStr;
|
||||
let idStr = Date.now().toString(36);
|
||||
idStr += Math.random().toString(36).substring(3, length);
|
||||
return idStr;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -520,7 +514,7 @@ const getNonDuplicateID = (length = 8) => {
|
||||
* @param {String} unit 单位 px em rem
|
||||
*/
|
||||
const addUnit = (value: string | number, unit = 'px') => {
|
||||
return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
|
||||
return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
|
||||
};
|
||||
|
||||
// 统一批量导出
|
||||
|
@ -35,12 +35,6 @@ service.interceptors.request.use(
|
||||
config.headers![CommonHeaderEnum.AUTHORIZATION] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 统一增加TENANT-ID请求头
|
||||
const tenantId = Session.getTenant();
|
||||
if (tenantId) {
|
||||
config.headers![CommonHeaderEnum.TENANT_ID] = tenantId;
|
||||
}
|
||||
|
||||
// 请求报文加密
|
||||
if (config.headers![CommonHeaderEnum.ENC_FLAG]) {
|
||||
const enc = other.encryption(JSON.stringify(config.data), import.meta.env.VITE_PWD_ENC_KEY);
|
||||
|
@ -175,32 +175,31 @@ export const rule = {
|
||||
|
||||
export const getRegExp = function (validatorName) {
|
||||
const commonRegExp = {
|
||||
number: '/^[-]?\\d+(\\.\\d+)?$/',
|
||||
letter: '/^[A-Za-z]+$/',
|
||||
letterAndNumber: '/^[A-Za-z0-9]+$/',
|
||||
mobilePhone: '/^[1][3-9][0-9]{9}$/',
|
||||
letterStartNumberIncluded: '/^[A-Za-z]+[A-Za-z\\d]*$/',
|
||||
noChinese: '/^[^\u4e00-\u9fa5]+$/',
|
||||
chinese: '/^[\u4e00-\u9fa5]+$/',
|
||||
email: '/^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$/',
|
||||
url: '/^(http:\\/\\/|https:\\/\\/)[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?|^((http:\\/\\/|https:\\/\\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(:\\d{0,5})?(\\/.*)?$/',
|
||||
number: '^[-]?\\d+(\\.\\d+)?$',
|
||||
letter: '^[A-Za-z]+$',
|
||||
letterAndNumber: '^[A-Za-z0-9]+$',
|
||||
mobilePhone: '^[1][3-9][0-9]{9}$',
|
||||
letterStartNumberIncluded: '^[A-Za-z]+[A-Za-z\\d]*$',
|
||||
noChinese: '^[^\u4e00-\u9fa5]+$',
|
||||
chinese: '^[\u4e00-\u9fa5]+$',
|
||||
email: '^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$',
|
||||
url: '(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]'
|
||||
};
|
||||
return commonRegExp[validatorName];
|
||||
};
|
||||
|
||||
const validateFn = (validatorName, rule, value, callback, defaultErrorMsg) => {
|
||||
if (validateNull(value) || value.length <= 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (validateNull(value) || value.length <= 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
const reg = new RegExp(getRegExp(validatorName));
|
||||
const reg = new RegExp(getRegExp(validatorName));
|
||||
|
||||
if (!reg.test(value)) {
|
||||
const errTxt = rule.errorMsg || defaultErrorMsg;
|
||||
callback(new Error(errTxt));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
if (!reg.test(value)) {
|
||||
const errTxt = rule.errorMsg || defaultErrorMsg;
|
||||
callback(new Error(errTxt));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
export default {
|
||||
audit: {
|
||||
index: '#',
|
||||
importsysAuditLogTip: 'import SysAuditLog',
|
||||
id: 'id',
|
||||
auditName: 'auditName',
|
||||
auditField: 'auditField',
|
||||
beforeVal: 'beforeVal',
|
||||
afterVal: 'afterVal',
|
||||
createBy: 'createBy',
|
||||
createTime: 'createTime',
|
||||
delFlag: 'delFlagx',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputAuditNameTip: 'input auditName',
|
||||
inputAuditFieldTip: 'input auditField',
|
||||
inputBeforeValTip: 'input beforeVal',
|
||||
inputAfterValTip: 'input afterVal',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputDelFlagTip: 'input delFlagx',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
export default {
|
||||
audit: {
|
||||
index: '#',
|
||||
importsysAuditLogTip: '导入审计记录表',
|
||||
id: '主键',
|
||||
auditName: '审计名称',
|
||||
auditField: '字段名称',
|
||||
beforeVal: '变更前值',
|
||||
afterVal: '变更后值',
|
||||
createBy: '操作人',
|
||||
createTime: '操作时间',
|
||||
delFlag: '删除标记',
|
||||
tenantId: '租户ID',
|
||||
inputIdTip: '请输入主键',
|
||||
inputAuditNameTip: '请输入审计名称',
|
||||
inputAuditFieldTip: '请输入字段名称',
|
||||
inputBeforeValTip: '请输入变更前值',
|
||||
inputAfterValTip: '请输入变更后值',
|
||||
inputCreateByTip: '请输入操作人',
|
||||
inputCreateTimeTip: '请输入操作时间',
|
||||
inputDelFlagTip: '请输入删除标记',
|
||||
inputTenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item :label="$t('audit.auditName')" prop="auditName">
|
||||
<el-input :placeholder="t('audit.inputAuditNameTip')" v-model="state.queryForm.auditName" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('audit.auditField')" prop="auditField">
|
||||
<el-input :placeholder="t('audit.inputAuditFieldTip')" v-model="state.queryForm.auditField" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('audit.createBy')" prop="createBy">
|
||||
<el-input :placeholder="t('audit.inputCreateByTip')" v-model="state.queryForm.createBy" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" formDialogRef @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button
|
||||
plain
|
||||
formDialogRef
|
||||
:disabled="multiple"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
class="ml10"
|
||||
v-auth="'sys_audit_del'"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_audit_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" :label="$t('audit.index')" width="80" />
|
||||
<el-table-column prop="auditName" :label="$t('audit.auditName')" show-overflow-tooltip />
|
||||
<el-table-column prop="auditField" :label="$t('audit.auditField')" show-overflow-tooltip />
|
||||
<el-table-column prop="beforeVal" :label="$t('audit.beforeVal')" show-overflow-tooltip />
|
||||
<el-table-column prop="afterVal" :label="$t('audit.afterVal')" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" :label="$t('audit.createBy')" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" :label="$t('audit.createTime')" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="delete" text type="primary" v-auth="'sys_audit_del'" @click="handleDelete([scope.row.id])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysAuditLog">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/admin/audit';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/audit/export', state.queryForm, 'audit.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
@ -60,47 +60,6 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-collapse v-model="collapseActive">
|
||||
<el-collapse-item name="1" title="安全属性">
|
||||
<template #title>
|
||||
<el-icon class="header-icon">
|
||||
<info-filled />
|
||||
</el-icon>
|
||||
安全属性
|
||||
</template>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.captchaFlag')" prop="captchaFlag">
|
||||
<el-radio-group v-model="form.captchaFlag">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in captcha_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.encFlag')" prop="encFlag">
|
||||
<el-radio-group v-model="form.encFlag">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in enc_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.onlineQuantity')" prop="onlineQuantity">
|
||||
<el-radio-group v-model="form.onlineQuantity">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in enc_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@ -129,11 +88,9 @@ const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { grant_types, common_status, captcha_flag_types, enc_flag_types } = useDict(
|
||||
const { grant_types, common_status } = useDict(
|
||||
'grant_types',
|
||||
'common_status',
|
||||
'captcha_flag_types',
|
||||
'enc_flag_types'
|
||||
);
|
||||
|
||||
// 提交表单数据
|
||||
@ -159,8 +116,6 @@ const form = reactive({
|
||||
encFlag: '1',
|
||||
});
|
||||
|
||||
const collapseActive = ref('1');
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
clientId: [
|
||||
@ -187,9 +142,6 @@ const dataRules = ref({
|
||||
{ required: true, message: '刷新时效不能为空', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '刷新时效不能小于两小时', trigger: 'blur' },
|
||||
],
|
||||
captchaFlag: [{ required: true, message: '是否开启验证码校验', trigger: 'blur' }],
|
||||
encFlag: [{ required: true, message: '是否开启密码加密传输', trigger: 'blur' }],
|
||||
onlineQuantity: [{ required: true, message: '是否允许同时在线', trigger: 'blur' }],
|
||||
autoapprove: [{ required: true, message: '自动放行不能为空', trigger: 'blur' }],
|
||||
webServerRedirectUri: [{ required: true, message: '回调地址不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('i18n.name')" prop="name">
|
||||
<el-input :placeholder="t('i18n.inputKeyTip')" v-model="form.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('i18n.zhCn')" prop="zhCn">
|
||||
<el-input :placeholder="t('i18n.inputZhCnTip')" v-model="form.zhCn" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('i18n.en')" prop="en">
|
||||
<el-input :placeholder="t('i18n.inputEnTip')" v-model="form.en" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="SysI18nDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateName, validateZhCn, validateEn } from '/@/api/admin/i18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
zhCn: '',
|
||||
en: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [
|
||||
{ required: true, message: 'name不能为空', trigger: 'blur' },
|
||||
{ validator: rule.noChinese, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateName(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
zhCn: [
|
||||
{ required: true, message: '中文不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateZhCn(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
en: [
|
||||
{ required: true, message: '英文不能为空', trigger: 'blur' },
|
||||
{ validator: rule.letter, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateEn(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysI18n信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getsysI18nData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getsysI18nData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
@ -1,24 +0,0 @@
|
||||
export default {
|
||||
i18n: {
|
||||
index: '#',
|
||||
importsysI18nTip: 'import SysI18n',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
zhCn: 'zh-cn',
|
||||
en: 'en',
|
||||
createBy: 'createBy',
|
||||
createTime: 'createTime',
|
||||
updateBy: 'updateBy',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
inputIdTip: 'input id',
|
||||
inputKeyTip: 'input key',
|
||||
inputZhCnTip: 'input zh-cn',
|
||||
inputEnTip: 'input en',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
},
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
export default {
|
||||
i18n: {
|
||||
index: '#',
|
||||
id: 'id',
|
||||
name: '名称',
|
||||
zhCn: '中文',
|
||||
en: '英文',
|
||||
createBy: '创建人',
|
||||
createTime: '创建时间',
|
||||
updateBy: '修改人',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '删除标记',
|
||||
inputIdTip: '请输入id',
|
||||
inputKeyTip: '请输入key',
|
||||
inputZhCnTip: '请输入中文',
|
||||
inputEnTip: '请输入英文',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入删除标记',
|
||||
importsysI18nTip: '导入系统表-国际化',
|
||||
},
|
||||
};
|
@ -1,166 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('i18n.name')" prop="name">
|
||||
<el-input :placeholder="t('i18n.inputKeyTip')" style="max-width: 180px" v-model="state.queryForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('i18n.zhCn')" prop="zhCn">
|
||||
<el-input :placeholder="t('i18n.inputZhCnTip')" style="max-width: 180px" v-model="state.queryForm.zhCn" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('i18n.en')" prop="en">
|
||||
<el-input :placeholder="t('i18n.inputEnTip')" style="max-width: 180px" v-model="state.queryForm.en" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" formDialogRef icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }} </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" formDialogRef icon="folder-add" type="primary" v-auth="'sys_i18n_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
formDialogRef
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'sys_i18n_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_i18n_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('file.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('i18n.name')" prop="name" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.zhCn')" prop="zhCn" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.en')" prop="en" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.createBy')" prop="createBy" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.id)" text type="primary" v-auth="'sys_i18n_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.id])" text type="primary" v-auth="'sys_i18n_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemSysI18n" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList, refreshCache } from '/@/api/admin/i18n';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
name: '',
|
||||
zhCn: '',
|
||||
en: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value.resetFields();
|
||||
// 清空排序规则
|
||||
state.queryForm!.descs = [];
|
||||
state.queryForm!.ascs = [];
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
const handleRefreshCache = () => {
|
||||
refreshCache().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/i18n/export', state.queryForm, 'i18n.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog :title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false" draggable>
|
||||
<el-dialog :title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false" :destroy-on-close="true" draggable>
|
||||
<el-form ref="menuDialogFormRef" :model="state.ruleForm" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
@ -30,6 +30,11 @@
|
||||
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputNameTip')"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysmenu.enName')" prop="enName">
|
||||
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputEnNameTip')"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20" v-if="state.ruleForm.menuType === '0'">
|
||||
<el-form-item :label="$t('sysmenu.path')" prop="path">
|
||||
<el-input v-model="state.ruleForm.path" :placeholder="$t('sysmenu.inputPathTip')" />
|
||||
@ -105,6 +110,7 @@ const state = reactive({
|
||||
ruleForm: {
|
||||
menuId: '',
|
||||
name: '',
|
||||
enName: '',
|
||||
permission: '',
|
||||
parentId: '',
|
||||
icon: '',
|
||||
|
@ -2,12 +2,14 @@ export default {
|
||||
sysmenu: {
|
||||
index: '#',
|
||||
name: 'menu name',
|
||||
enName: 'en menu name',
|
||||
sortOrder: 'sortOrder',
|
||||
path: 'path',
|
||||
menuType: 'menuType',
|
||||
keepAlive: 'keepAlive',
|
||||
permission: 'permission',
|
||||
inputNameTip: 'input name',
|
||||
inputEnNameTip: 'input en name',
|
||||
parentId: 'parent menu',
|
||||
embedded: 'embedded',
|
||||
visible: 'visible',
|
||||
|
@ -2,12 +2,14 @@ export default {
|
||||
sysmenu: {
|
||||
index: '#',
|
||||
name: '菜单名称',
|
||||
enName: '菜单英文',
|
||||
sortOrder: '排序',
|
||||
path: '路由',
|
||||
menuType: '类型',
|
||||
keepAlive: '缓冲',
|
||||
permission: '权限标识',
|
||||
inputNameTip: '请输入菜单名称',
|
||||
inputEnNameTip: '请输入菜单英文',
|
||||
parentId: '上级菜单',
|
||||
embedded: '是否内嵌',
|
||||
visible: '是否显示',
|
||||
|
@ -27,28 +27,6 @@
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24" :md="24" :sm="24" :xl="24" :xs="24" class="mb20">
|
||||
<el-form-item :label="$t('sysrole.menu_authority')" prop="dsType">
|
||||
<el-select :placeholder="$t('sysrole.please_select')" class="w100" clearable v-model="form.dsType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in dictType" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20" v-if="form.dsType === 1">
|
||||
<el-form-item>
|
||||
<el-tree
|
||||
:check-strictly="true"
|
||||
:data="dataForm.deptData"
|
||||
:default-checked-keys="dataForm.checkedDsScope"
|
||||
:props="dataForm.deptProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
ref="deptTreeRef"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@ -84,7 +62,6 @@ const form = reactive({
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleDesc: '',
|
||||
dsType: 0,
|
||||
dsScope: '',
|
||||
});
|
||||
|
||||
@ -122,34 +99,9 @@ const dataRules = ref({
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleDesc: [{ max: 128, message: '长度在 128 个字符内', trigger: 'blur' }],
|
||||
dsType: [{ required: true, message: '请选择数据权限类型', trigger: 'blur' }],
|
||||
menu_authority: [{ required: true, message: '数据权限不能为空', trigger: 'blur' }],
|
||||
roleDesc: [{ max: 128, message: '长度在 128 个字符内', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: 4,
|
||||
},
|
||||
]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
@ -170,12 +122,6 @@ const openDialog = (id: string) => {
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
if (form.dsType === 1) {
|
||||
form.dsScope = deptTreeRef.value.getCheckedKeys().join(',');
|
||||
} else {
|
||||
form.dsScope = '';
|
||||
}
|
||||
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
|
@ -49,11 +49,6 @@
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<template #default="scope">
|
||||
@ -129,29 +124,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: '4',
|
||||
},
|
||||
]);
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
|
@ -1,28 +1,37 @@
|
||||
<template>
|
||||
<div class="system-role-dialog-container">
|
||||
<el-dialog :title="state.dialog.title" width="30%" v-model="state.dialog.isShowDialog" :close-on-click-modal="false" draggable>
|
||||
<el-tree
|
||||
v-loading="loading"
|
||||
ref="menuTree"
|
||||
:data="state.treeData"
|
||||
:default-checked-keys="state.checkedKeys"
|
||||
:check-strictly="false"
|
||||
:props="state.defaultProps"
|
||||
class="filter-tree"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<div class="system-role-dialog-container">
|
||||
<el-dialog width="30%" v-model="state.dialog.isShowDialog" :close-on-click-modal="false" draggable>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ state.dialog.title }}</div>
|
||||
<div class="flex mr-16">
|
||||
<el-checkbox label="展开/折叠" @change="handleExpand" />
|
||||
<el-checkbox label="全选/不全选" @change="handleSelectAll" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar class="h-[400px] sm:h-[600px]">
|
||||
<el-tree
|
||||
v-loading="loading"
|
||||
ref="menuTree"
|
||||
:data="state.treeData"
|
||||
:default-checked-keys="state.checkedKeys"
|
||||
:check-strictly="!checkStrictly"
|
||||
:props="state.defaultProps"
|
||||
class="filter-tree"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
show-checkbox
|
||||
/>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="state.dialog.isShowDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">{{ state.dialog.submitTxt }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="role-permession">
|
||||
@ -32,68 +41,86 @@ import { useMessage } from '/@/hooks/message';
|
||||
import { Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const menuTree = ref();
|
||||
const checkStrictly = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
checkedKeys: [] as any[],
|
||||
treeData: [] as any[],
|
||||
defaultProps: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
roleId: '',
|
||||
dialog: {
|
||||
isShowDialog: false,
|
||||
title: '分配权限',
|
||||
submitTxt: '更新',
|
||||
},
|
||||
checkedKeys: [] as any[],
|
||||
treeData: [] as any[],
|
||||
defaultProps: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
roleId: '',
|
||||
dialog: {
|
||||
isShowDialog: false,
|
||||
title: '分配权限',
|
||||
submitTxt: '更新',
|
||||
},
|
||||
});
|
||||
|
||||
const checkedKeys: Ref<any[]> = ref([]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any) => {
|
||||
state.checkedKeys = [];
|
||||
state.treeData = [];
|
||||
checkedKeys.value = [];
|
||||
state.roleId = row.roleId;
|
||||
loading.value = true;
|
||||
fetchRoleTree(row.roleId)
|
||||
.then((res) => {
|
||||
checkedKeys.value = res.data;
|
||||
return pageList();
|
||||
})
|
||||
.then((r) => {
|
||||
state.treeData = r.data;
|
||||
state.checkedKeys = other.resolveAllEunuchNodeId(state.treeData, checkedKeys.value, []);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
state.dialog.isShowDialog = true;
|
||||
state.checkedKeys = [];
|
||||
state.treeData = [];
|
||||
checkedKeys.value = [];
|
||||
state.roleId = row.roleId;
|
||||
loading.value = true;
|
||||
fetchRoleTree(row.roleId)
|
||||
.then((res) => {
|
||||
checkedKeys.value = res.data;
|
||||
return pageList();
|
||||
})
|
||||
.then((r) => {
|
||||
state.treeData = r.data;
|
||||
state.checkedKeys = other.resolveAllEunuchNodeId(state.treeData, checkedKeys.value, []);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
state.dialog.isShowDialog = true;
|
||||
};
|
||||
|
||||
const handleExpand = (check: CheckboxValueType) => {
|
||||
const treeList = state.treeData;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
//@ts-ignore
|
||||
menuTree.value.store.nodesMap[treeList[i].id].expanded = check;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = (check: CheckboxValueType) => {
|
||||
if (check) {
|
||||
menuTree.value?.setCheckedKeys(state.treeData.map((item) => item.id));
|
||||
} else {
|
||||
menuTree.value?.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交授权数据
|
||||
const onSubmit = () => {
|
||||
const menuIds = menuTree.value.getCheckedKeys().join(',').concat(',').concat(menuTree.value.getHalfCheckedKeys().join(','));
|
||||
loading.value = true;
|
||||
permissionUpd(state.roleId, menuIds)
|
||||
.then(() => {
|
||||
state.dialog.isShowDialog = false;
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
const menuIds = menuTree.value.getCheckedKeys().join(',').concat(',').concat(menuTree.value.getHalfCheckedKeys().join(','));
|
||||
loading.value = true;
|
||||
permissionUpd(state.roleId, menuIds)
|
||||
.then(() => {
|
||||
state.dialog.isShowDialog = false;
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -1,149 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.type')" prop="type">
|
||||
<el-select :placeholder="t('social.inputTypeTip')" v-model="form.type">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in social_type">
|
||||
{{ item.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.remark')" prop="remark">
|
||||
<el-input :placeholder="t('social.inputRemarkTip')" v-model="form.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.appId')" prop="appId">
|
||||
<el-input :placeholder="t('social.inputAppIdTip')" v-model="form.appId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.appSecret')" prop="appSecret">
|
||||
<el-input :placeholder="t('social.inputAppSecretTip')" v-model="form.appSecret" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('social.redirectUrl')" prop="redirectUrl">
|
||||
<el-input :placeholder="t('social.inputRedirectUrlTip')" type="textarea" v-model="form.redirectUrl" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('social.ext')" prop="ext">
|
||||
<el-input :placeholder="t('social.inputExtTip')" type="textarea" v-model="form.ext" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="AppSocialDetailsDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj } from '/@/api/admin/social';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { social_type } = useDict('social_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
type: '',
|
||||
remark: '',
|
||||
appId: '' as string | undefined,
|
||||
appSecret: '' as string | undefined,
|
||||
redirectUrl: '',
|
||||
ext: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
|
||||
appId: [{ required: true, message: 'appId不能为空', trigger: 'blur' }],
|
||||
remark: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
|
||||
redirectUrl: [
|
||||
{ required: true, message: '回调地址不能为空', trigger: 'blur' },
|
||||
{ validator: rule.url, trigger: 'blur' },
|
||||
],
|
||||
appSecret: [{ required: true, message: 'appSecret不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取appSocialDetails信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getappSocialDetailsData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
// 隐藏敏感信息
|
||||
form.appSecret = form.appSecret?.includes('******') ? undefined : form.appSecret;
|
||||
form.appId = form.appId?.includes('******') ? undefined : form.appId;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
if (form.id) {
|
||||
await putObj(form);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
} else {
|
||||
await addObj(form);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
}
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getappSocialDetailsData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
@ -1,32 +0,0 @@
|
||||
export default {
|
||||
social: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: 'import AppSocialDetails',
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
remark: 'remark',
|
||||
appId: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
redirectUrl: 'redirectUrl',
|
||||
ext: 'ext',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputTypeTip: 'input type',
|
||||
inputRemarkTip: 'input remark',
|
||||
inputAppIdTip: 'input appId',
|
||||
inputAppSecretTip: 'input appSecret',
|
||||
inputRedirectUrlTip: 'input redirectUrl',
|
||||
inputExtTip: 'input ext',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
export default {
|
||||
social: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: '导入系统社交登录账号表',
|
||||
id: '主鍵',
|
||||
type: '类型',
|
||||
remark: '描述',
|
||||
appId: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
redirectUrl: '回调地址',
|
||||
ext: '拓展字段',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '${field.fieldComment}',
|
||||
tenantId: '所属租户',
|
||||
inputIdTip: '请输入主鍵',
|
||||
inputTypeTip: '请输入类型',
|
||||
inputRemarkTip: '请输入描述',
|
||||
inputAppIdTip: '请输入appId',
|
||||
inputAppSecretTip: '请输入appSecret',
|
||||
inputRedirectUrlTip: '请输入回调地址',
|
||||
inputExtTip: '请输入拓展字段',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入${field.fieldComment}',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
},
|
||||
};
|
@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form ref="queryRef" :inline="true" :model="state.queryForm">
|
||||
<el-form-item :label="t('social.type')" class="ml2" prop="type">
|
||||
<el-select v-model="state.queryForm.type" :placeholder="t('social.inputTypeTip')">
|
||||
<el-option v-for="(item, index) in social_type" :key="index" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button formDialogRef icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button v-auth="'sys_social_details_add'" class="ml10" icon="folder-add" type="primary" @click="formDialogRef.openDialog()">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'sys_social_details_del'"
|
||||
:disabled="multiple"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'sys_social_details_del'"
|
||||
@exportExcel="exportExcel"
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
v-loading="state.loading"
|
||||
:data="state.dataList"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('social.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('social.type')" prop="type" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="social_type" :value="scope.row.type"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('social.remark')" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.appId')" prop="appId" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.appSecret')" prop="appSecret" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" v-auth="'sys_social_details_edit'" text type="primary" @click="formDialogRef.openDialog(scope.row.id)"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" v-auth="'sys_social_details_del'" text type="primary" @click="handleDelete([scope.row.id])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemAppSocialDetails" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/admin/social';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
const { social_type } = useDict('social_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/social/export', state.queryForm, 'social.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,177 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.name')" prop="name">
|
||||
<el-input v-model="form.name" :placeholder="t('tenant.inputnameTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.code')" prop="code">
|
||||
<el-input v-model="form.code" :placeholder="t('tenant.inputcodeTip')" :disabled="form.id !== ''" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :span="12" :label="t('tenant.tenantDomain')" prop="tenantDomain">
|
||||
<el-input v-model="form.tenantDomain" :placeholder="t('tenant.inputtenantDomainTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :span="12" :label="t('tenant.menuId')" prop="menuId">
|
||||
<el-select v-model="form.menuId" :placeholder="t('tenant.inputmenuIdTip')" clearable class="w100">
|
||||
<el-option v-for="item in menuData" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.startTime')" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="date" :placeholder="t('tenant.inputstartTimeTip')" :value-format="dateTimeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.endTime')" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="date" :placeholder="t('tenant.inputendTimeTip')" :value-format="dateTimeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.status')" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="item.value" v-for="(item, index) in status_type" border :key="index">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemTenantDialog">
|
||||
import { validateTenantCode, validateTenantName } from '/@/api/admin/tenant';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj } from '/@/api/admin/tenant';
|
||||
import { menuList } from '/@/api/admin/tenant-menu';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
tenantDomain: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
status: '0',
|
||||
delFlag: '',
|
||||
createBy: '',
|
||||
updateBy: '',
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
menuId: '',
|
||||
});
|
||||
|
||||
const menuData = ref([]);
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [
|
||||
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantName(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '编码不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantCode(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: 'status不能为空', trigger: 'blur' }],
|
||||
menuId: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string): void => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantData(id);
|
||||
}
|
||||
getMenuList();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化表格数据。
|
||||
* @param {string} id - 部门 ID。
|
||||
*/
|
||||
const getTenantData = async (id) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取菜单列表数据。
|
||||
*/
|
||||
const getMenuList = async () => {
|
||||
const res = await menuList();
|
||||
menuData.value = res.data;
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
@ -1,39 +0,0 @@
|
||||
export default {
|
||||
tenant: {
|
||||
index: '#',
|
||||
importTenantTip: ' import Tenant',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
code: 'code',
|
||||
tenantDomain: 'tenantDomain',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
status: 'status',
|
||||
delFlag: 'delFlag',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
menuId: 'menuIds',
|
||||
inputidTip: 'input id',
|
||||
inputnameTip: 'input name',
|
||||
inputcodeTip: 'input code',
|
||||
inputtenantDomainTip: 'input tenantDomain',
|
||||
inputstartTimeTip: 'input startTime',
|
||||
inputendTimeTip: 'input endTime',
|
||||
inputstatusTip: 'input status',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputmenuIdTip: 'input menuId',
|
||||
deleteDisabledTip: 'base tenants are not allowed to delete',
|
||||
},
|
||||
tenantmenu: {
|
||||
name: 'tenantmenu',
|
||||
index: '#',
|
||||
status: 'status',
|
||||
createTime: 'createTime',
|
||||
},
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
export default {
|
||||
tenant: {
|
||||
index: '#',
|
||||
importTenantTip: '导入租户',
|
||||
id: '租户id',
|
||||
name: '租户名称',
|
||||
code: '编码',
|
||||
tenantDomain: '域名',
|
||||
startTime: '开始时间',
|
||||
endTime: '结束时间',
|
||||
status: '状态',
|
||||
delFlag: 'delFlag',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建',
|
||||
updateTime: '更新时间',
|
||||
menuId: '租户套餐',
|
||||
inputidTip: '请输入租户id',
|
||||
inputnameTip: '请输入名称',
|
||||
inputcodeTip: '请输入编码',
|
||||
inputtenantDomainTip: '请输入域名',
|
||||
inputstartTimeTip: '请输入开始时间',
|
||||
inputendTimeTip: '请输入结束时间',
|
||||
inputstatusTip: '请输入status',
|
||||
inputdelFlagTip: '请输入delFlag',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateByTip: '请输入修改人',
|
||||
inputcreateTimeTip: '请输入创建',
|
||||
inputupdateTimeTip: '请输入更新时间',
|
||||
inputmenuIdTip: '请选择租户套餐',
|
||||
deleteDisabledTip: '基础租户不允许删除',
|
||||
},
|
||||
tenantmenu: {
|
||||
name: '租户套餐',
|
||||
index: '#',
|
||||
status: '状态',
|
||||
createTime: '创建',
|
||||
},
|
||||
};
|
@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('tenant.name')" prop="name">
|
||||
<el-input :placeholder="$t('tenant.inputnameTip')" style="max-width: 180px" v-model="state.queryForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'sys_systenant_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button plain @click="handleTenantMenu()" class="ml10" type="primary" v-auth="'sys_systenant_tenantmenu'">
|
||||
{{ $t('tenantmenu.name') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_systenant_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column :selectable="handleSelectable" align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('tenant.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('tenant.name')" prop="name" show-overflow-tooltip />
|
||||
<el-table-column :label="t('tenant.code')" prop="code" show-overflow-tooltip />
|
||||
<el-table-column :label="t('tenant.tenantDomain')" prop="tenantDomain" show-overflow-tooltip />
|
||||
<el-table-column :label="t('tenant.startTime')" prop="startTime" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ parseDate(scope.row.startTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('tenant.endTime')" prop="endTime" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ parseDate(scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('tenant.status')" prop="status" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="status_type" :value="scope.row.status"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="200">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.id)" text type="primary" v-auth="'sys_systenant_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-tooltip :content="$t('tenant.deleteDisabledTip')" :disabled="scope.row.id !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
:disabled="scope.row.id === '1'"
|
||||
@click="handleDelete([scope.row.id])"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
<!-- 导入excel -->
|
||||
<upload-excel
|
||||
:title="$t('tenant.importTenantTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/tenant.xlsx"
|
||||
url="/admin/tenant/import"
|
||||
/>
|
||||
|
||||
<tenant-menu ref="TenantMenuRef"></tenant-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemTenant" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchPage } from '/@/api/admin/tenant';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const TenantMenu = defineAsyncComponent(() => import('./tenantMenu/index.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const TenantMenuRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchPage,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.id !== '1';
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/tenant/export', state.queryForm, 'tenant.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTenantMenu = () => {
|
||||
TenantMenuRef.value.open();
|
||||
};
|
||||
</script>
|
@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.name')" prop="name">
|
||||
<el-input v-model="form.name" :placeholder="t('tenant.inputnameTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.status')" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="item.value" border v-for="(item, index) in status_type" :key="index">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item prop="menuIds">
|
||||
<el-tree
|
||||
show-checkbox
|
||||
ref="menuTreeRef"
|
||||
:check-strictly="false"
|
||||
:data="menuData"
|
||||
:props="defaultProps"
|
||||
:default-checked-keys="checkedMenu"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tenant-menu-form">
|
||||
import { treemenu, addObj, putObj } from '/@/api/admin/tenant-menu';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj } from '/@/api/admin/tenant-menu';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
const { status_type } = useDict('status_type');
|
||||
const { t } = useI18n();
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const menuTreeRef = ref();
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
status: '0',
|
||||
name: '',
|
||||
menuIds: '',
|
||||
});
|
||||
|
||||
const dataRules = reactive({
|
||||
name: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '套餐状态不能为空', trigger: 'blur' }],
|
||||
menuIds: [
|
||||
{
|
||||
validator: (_rule: any, _value: any, callback: any) => {
|
||||
if (menuTreeRef.value.getCheckedKeys().length > 0) {
|
||||
callback();
|
||||
} else {
|
||||
useMessage().error('请选择租户套餐菜单');
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const menuData = ref<any[]>([]);
|
||||
|
||||
const defaultProps = reactive({
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
});
|
||||
|
||||
const checkedMenu = ref<any[]>([]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
checkedMenu.value = [];
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取Tenant信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantMenuData(id);
|
||||
}
|
||||
|
||||
getMenuData();
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
// 获取已经选择的节点
|
||||
form.menuIds = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()].join(',');
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取菜单数据
|
||||
*/
|
||||
const getMenuData = async () => {
|
||||
const res = await treemenu();
|
||||
menuData.value = res.data;
|
||||
checkedMenu.value = form.menuIds ? other.resolveAllEunuchNodeId(menuData.value, form.menuIds.split(','), []) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取部门下的菜单数据
|
||||
* @param {string} id - 部门 ID
|
||||
*/
|
||||
const getTenantMenuData = async (id: string) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data[0]);
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="租户套餐" size="80%">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="tenantMenuDialogRef.openDialog()" v-auth="'sys_systenantmenu_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar :search="false" class="ml10" style="float: right; margin-right: 20px" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="index" :label="$t('tenantmenu.index')" width="80" />
|
||||
<el-table-column prop="name" :label="$t('tenantmenu.name')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('tenantmenu.status')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="status_type" :value="scope.row.status"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('tenantmenu.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" @click="tenantMenuDialogRef.openDialog(scope.row.id)" v-auth="'sys_systenantmenu_edit'">
|
||||
{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" text type="primary" @click="handleDelete(scope.row)" v-auth="'sys_systenantmenu_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"> </pagination>
|
||||
<tenant-menu-dialog ref="tenantMenuDialogRef" @refresh="getDataList"></tenant-menu-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tenant-menu">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObj } from '/@/api/admin/tenant-menu';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const TenantMenuDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
const { status_type } = useDict('status_type');
|
||||
const visible = ref(false);
|
||||
const tenantMenuDialogRef = ref();
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: fetchList,
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
||||
|
||||
/**
|
||||
* 处理删除事件。
|
||||
* @param {Object} row - 要删除的数据行对象。
|
||||
*/
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(row.id);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@ -45,7 +45,7 @@
|
||||
<el-table-column prop="createTime" :label="t('log.createTime')" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" v-auth="'pigx_log_edit'" @click="formDialogRef.openDialog(scope.row.jobLogId)"
|
||||
<el-button text type="primary" v-auth="'pix_log_edit'" @click="formDialogRef.openDialog(scope.row.jobLogId)"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button text type="primary" v-auth="'sys_log_del'" @click="handleDelete([scope.row.jobLogId])">{{ $t('common.delBtn') }} </el-button>
|
||||
|
@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<el-drawer title="设计历史" v-model="visible" draggable>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%" @sort-change="sortChangeHandle">
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
<el-table-column prop="createTime" label="设计时间" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="refresh" text type="primary" @click="handleRollback(scope.row.id)">回滚</el-button>
|
||||
<el-button icon="delete" text type="primary" @click="handleDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemFormHistoryDialog">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delFormObj, fetchFormList } from '/@/api/gen/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
// 搜索变量
|
||||
const visible = ref(false);
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchFormList,
|
||||
createdIsNeed: false,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle } = useTable(state);
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delFormObj(id);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 回滚
|
||||
const handleRollback = (id: string) => {
|
||||
emit('refresh', id);
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (dsName: string, tableName: string) => {
|
||||
visible.value = true;
|
||||
state.queryForm.dsName = dsName;
|
||||
state.queryForm.tableName = tableName;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<v-form-designer ref="vfDesignerRef" :banned-widgets="bannedWidgets" :designer-config="designerConfig">
|
||||
<template #customToolButtons>
|
||||
<el-button link type="primary" @click="saveJsonConfig">保存</el-button>
|
||||
<el-button link type="primary" @click="exportSfcConfig">导出</el-button>
|
||||
<el-button link type="primary" @click="formDialogRef.openDialog(dsName, tableName)">历史</el-button>
|
||||
</template>
|
||||
</v-form-designer>
|
||||
</div>
|
||||
<form-dialog ref="formDialogRef" @refresh="handleRefresh" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { fetchFormById, useFormConfSaveApi, useGeneratorVFormApi, useGeneratorVFormSfcApi } from '/@/api/gen/table';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { handleBlobFile } from '/@/utils/other';
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const vfDesignerRef = ref();
|
||||
|
||||
const formDialogRef = ref();
|
||||
|
||||
const bannedWidgets = reactive(['tab', 'card', 'table', 'cascader']);
|
||||
|
||||
const designerConfig = reactive({
|
||||
//是否显示语言切换菜单
|
||||
languageMenu: false,
|
||||
|
||||
//是否显示GitHub、文档等外部链接
|
||||
externalLink: false,
|
||||
|
||||
//是否显示表单模板
|
||||
formTemplates: false,
|
||||
|
||||
//是否禁止修改唯一名称
|
||||
widgetNameReadonly: false,
|
||||
|
||||
//是否显示组件事件属性折叠面板
|
||||
eventCollapse: false,
|
||||
|
||||
//是否显示清空设计器按钮
|
||||
clearDesignerButton: true,
|
||||
|
||||
//是否显示预览表单按钮
|
||||
previewFormButton: false,
|
||||
|
||||
//是否显示导入JSON按钮
|
||||
importJsonButton: false,
|
||||
|
||||
//是否显示导出JSON器按钮
|
||||
exportJsonButton: false,
|
||||
|
||||
//是否显示导出代码按钮
|
||||
exportCodeButton: true,
|
||||
|
||||
//是否显示生成SFC按钮
|
||||
generateSFCButton: false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
importJsonConfig();
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const tableName = ref();
|
||||
const dsName = ref();
|
||||
// 根据当前表,获取json配置信息
|
||||
const importJsonConfig = () => {
|
||||
const { tableName, dsName } = route.query;
|
||||
|
||||
if (tableName && tableName) {
|
||||
useGeneratorVFormApi(dsName, tableName).then((res) => {
|
||||
vfDesignerRef.value.loadJson(res);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = (id: string) => {
|
||||
fetchFormById(id).then((res) => {
|
||||
vfDesignerRef.value.loadJson(JSON.parse(res.data.formInfo));
|
||||
});
|
||||
};
|
||||
|
||||
const saveJsonConfig = () => {
|
||||
const { tableName, dsName } = route.query;
|
||||
|
||||
if (tableName && dsName) {
|
||||
// 先保存表单
|
||||
const formJson = vfDesignerRef.value.getFormJson();
|
||||
useFormConfSaveApi({
|
||||
dsName,
|
||||
tableName,
|
||||
formInfo: JSON.stringify(formJson),
|
||||
}).then(() => {
|
||||
useMessage().success(t('common.optSuccessText'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const exportSfcConfig = async () => {
|
||||
try {
|
||||
const { tableName, dsName } = route.query;
|
||||
if (!tableName || !dsName) throw new Error('表名或数据源名称不能为空');
|
||||
const formJson = vfDesignerRef.value.getFormJson();
|
||||
const { data } = await useFormConfSaveApi({
|
||||
dsName,
|
||||
tableName,
|
||||
formInfo: JSON.stringify(formJson),
|
||||
});
|
||||
const sfcRes = await useGeneratorVFormSfcApi(data.id);
|
||||
handleBlobFile(sfcRes, 'form.vue');
|
||||
} catch (error: any) {
|
||||
useMessage().error(error.message);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
@ -1,132 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" title="子表配置" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" ref="dataFormRef">
|
||||
<el-row :gutter="35">
|
||||
<el-col :span="8">
|
||||
<el-form-item prop="childTableName">
|
||||
<template #label> 子表名<tip content="关联表的子表,例如一对多中的存储多信息的表" /> </template>
|
||||
<el-select placeholder="请选择子表" v-model="form.childTableName" filterable @change="getChildTableColumnList">
|
||||
<el-option :key="item.tableName" :label="item.tableName" :value="item.tableName" v-for="item in childTableList"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item prop="mainField">
|
||||
<template #label> 主表字段<tip content="一般为主表的主键字段" /> </template>
|
||||
<el-select placeholder="请选关联字段" v-model="form.mainField" filterable>
|
||||
<el-option :key="item.columnName" :label="item.columnName" :value="item.columnName" v-for="item in mainTableColumnList"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item prop="childField">
|
||||
<template #label> 子表字段<tip content="子表中对应主表主键的关联字段" /> </template>
|
||||
<el-select placeholder="请选关联字段" v-model="form.childField" filterable>
|
||||
<el-option :key="item.columnName" :label="item.columnName" :value="item.columnName" v-for="item in childTableColumnList"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onClear">清空</el-button>
|
||||
<el-button @click="onSubmit" type="primary">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="child">
|
||||
import { useListTableApi, useListTableColumnApi } from '/@/api/gen/table';
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
defineProps({
|
||||
modelValue: Object,
|
||||
});
|
||||
|
||||
const visible = ref(false);
|
||||
const dataFormRef = ref();
|
||||
const form = reactive({
|
||||
childTableName: '',
|
||||
mainField: '',
|
||||
childField: '',
|
||||
});
|
||||
|
||||
const dataRules = ref({
|
||||
childTableName: [{ required: true, message: '请选择子表', trigger: 'blur' }],
|
||||
mainField: [{ required: true, message: '请选择关联字段', trigger: 'blur' }],
|
||||
childField: [{ required: true, message: '请选择关联字段', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const childTableList = ref();
|
||||
const childTableColumnList = ref();
|
||||
const mainTableColumnList = ref();
|
||||
const currentDsName = ref('');
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any) => {
|
||||
currentDsName.value = row.dsName;
|
||||
visible.value = true;
|
||||
|
||||
getAllTable(row.dsName);
|
||||
getAllField(row.dsName, row.tableName);
|
||||
if (row.childTableName) {
|
||||
form.childTableName = row.childTableName;
|
||||
form.mainField = row.mainField;
|
||||
form.childField = row.childField;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前数据下的所有表
|
||||
const getAllTable = (dsName: string) => {
|
||||
useListTableApi(dsName).then((res) => {
|
||||
childTableList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取对应表的所有字段
|
||||
const getAllField = (dsName: string, tableName: string) => {
|
||||
useListTableColumnApi(dsName, tableName).then((res) => {
|
||||
mainTableColumnList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取子表的全部字段
|
||||
const getChildTableColumnList = (val: string) => {
|
||||
form.childField = '';
|
||||
useListTableColumnApi(currentDsName.value, val).then((res) => {
|
||||
childTableColumnList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 确认提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
// 清空子表配置,后台当单表处理
|
||||
const onClear = () => {
|
||||
form.childTableName = '';
|
||||
form.mainField = '';
|
||||
form.childField = '';
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => form,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
@ -1,230 +1,210 @@
|
||||
<template>
|
||||
<el-form :model="dataForm" :rules="dataRules" label-width="120px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input disabled placeholder="表名" :value="tableNameStr"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button plain icon="Search" class="ml10" @click="childTableRef.openDialog(dataForm)"> 子表 </el-button>
|
||||
<!-- 配置子表 -->
|
||||
<child-table-config v-model="childForm" ref="childTableRef" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="说明" prop="tableComment">
|
||||
<el-input placeholder="说明" v-model="dataForm.tableComment"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="类名" prop="className">
|
||||
<el-input placeholder="类名" v-model="dataForm.className"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input placeholder="默认作者" v-model="dataForm.author"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="项目包名" prop="packageName">
|
||||
<el-input placeholder="项目包名" v-model="dataForm.packageName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="模块名" prop="moduleName">
|
||||
<el-input placeholder="模块名" v-model="dataForm.moduleName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="功能名" prop="functionName">
|
||||
<el-input placeholder="功能名" v-model="dataForm.functionName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="代码风格" prop="style">
|
||||
<el-select v-model="dataForm.style">
|
||||
<el-option :key="index" :label="item.groupName" :value="item.id" v-for="(item, index) in groupDataList"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="表单布局" prop="formLayout">
|
||||
<el-radio-group v-model="dataForm.formLayout">
|
||||
<el-radio-button :label="1">一列</el-radio-button>
|
||||
<el-radio-button :label="2">两列</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="生成方式" prop="generatorType">
|
||||
<el-radio-group v-model="dataForm.generatorType">
|
||||
<el-radio-button :label="1">自定义路径</el-radio-button>
|
||||
<el-radio-button :label="0">ZIP 压缩包</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="后端生成路径" prop="backendPath" v-if="dataForm.generatorType === 1">
|
||||
<el-input placeholder="后端生成路径" v-model="dataForm.backendPath"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="前端生成路径" prop="frontendPath" v-if="dataForm.generatorType === 1">
|
||||
<el-input placeholder="前端生成路径" v-model="dataForm.frontendPath"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-form :model="dataForm" :rules="dataRules" label-width="120px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input disabled placeholder="表名" :value="tableNameStr"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="说明" prop="tableComment">
|
||||
<el-input placeholder="说明" v-model="dataForm.tableComment"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="类名" prop="className">
|
||||
<el-input placeholder="类名" v-model="dataForm.className"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input placeholder="默认作者" v-model="dataForm.author"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="项目包名" prop="packageName">
|
||||
<el-input placeholder="项目包名" v-model="dataForm.packageName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="模块名" prop="moduleName">
|
||||
<el-input placeholder="模块名" v-model="dataForm.moduleName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="功能名" prop="functionName">
|
||||
<el-input placeholder="功能名" v-model="dataForm.functionName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="代码风格" prop="style">
|
||||
<el-select v-model="dataForm.style">
|
||||
<el-option :key="index" :label="item.groupName" :value="item.id"
|
||||
v-for="(item, index) in groupDataList"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="表单布局" prop="formLayout">
|
||||
<el-radio-group v-model="dataForm.formLayout">
|
||||
<el-radio-button :label="1">一列</el-radio-button>
|
||||
<el-radio-button :label="2">两列</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="生成方式" prop="generatorType">
|
||||
<el-radio-group v-model="dataForm.generatorType">
|
||||
<el-radio-button :label="1">自定义路径</el-radio-button>
|
||||
<el-radio-button :label="0">ZIP 压缩包</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="后端生成路径" prop="backendPath" v-if="dataForm.generatorType === 1">
|
||||
<el-input placeholder="后端生成路径" v-model="dataForm.backendPath"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="前端生成路径" prop="frontendPath" v-if="dataForm.generatorType === 1">
|
||||
<el-input placeholder="前端生成路径" v-model="dataForm.frontendPath"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { putObj, useTableApi } from '/@/api/gen/table';
|
||||
import { list as groupList } from '/@/api/gen/group';
|
||||
import { Local } from '/@/utils/storage';
|
||||
|
||||
const ChildTableConfig = defineAsyncComponent(() => import('./child.vue'));
|
||||
import {putObj, useTableApi} from '/@/api/gen/table';
|
||||
import {list as groupList} from '/@/api/gen/group';
|
||||
import {Local} from '/@/utils/storage';
|
||||
|
||||
const props = defineProps({
|
||||
tableName: {
|
||||
type: String,
|
||||
},
|
||||
dsName: {
|
||||
type: String,
|
||||
},
|
||||
tableName: {
|
||||
type: String,
|
||||
},
|
||||
dsName: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['refreshDataList']);
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const dataFormRef = ref();
|
||||
const childTableRef = ref();
|
||||
const childForm = ref();
|
||||
const tableNameStr = ref('');
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
generatorType: 0,
|
||||
formLayout: 1,
|
||||
backendPath: '',
|
||||
frontendPath: '',
|
||||
packageName: '',
|
||||
email: '',
|
||||
author: '',
|
||||
version: '',
|
||||
moduleName: '',
|
||||
functionName: '',
|
||||
className: '',
|
||||
tableComment: '',
|
||||
tableName: '' as string,
|
||||
dsName: '' as string,
|
||||
style: '', // 默认风格 element-plus
|
||||
childTableName: '',
|
||||
id: '',
|
||||
generatorType: 0,
|
||||
formLayout: 1,
|
||||
backendPath: '',
|
||||
frontendPath: '',
|
||||
packageName: '',
|
||||
email: '',
|
||||
author: '',
|
||||
version: '',
|
||||
moduleName: '',
|
||||
functionName: '',
|
||||
className: '',
|
||||
tableComment: '',
|
||||
tableName: '' as string,
|
||||
dsName: '' as string,
|
||||
style: '', // 默认风格 element-plus
|
||||
childTableName: '',
|
||||
});
|
||||
|
||||
const groupDataList = ref([]);
|
||||
const getTable = (dsName: string, tableName: string) => {
|
||||
loading.value = true;
|
||||
useTableApi(dsName, tableName)
|
||||
.then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
let list = res.data.groupList;
|
||||
dataForm.style = list[0].id;
|
||||
loading.value = true;
|
||||
useTableApi(dsName, tableName)
|
||||
.then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
let list = res.data.groupList;
|
||||
dataForm.style = list[0].id;
|
||||
|
||||
// 如果是保存路径的形式,有限使用本地配置项
|
||||
const frontendPath = Local.get('frontendPath');
|
||||
const backendPath = Local.get('backendPath');
|
||||
// 如果是保存路径的形式,有限使用本地配置项
|
||||
const frontendPath = Local.get('frontendPath');
|
||||
const backendPath = Local.get('backendPath');
|
||||
|
||||
if (frontendPath && backendPath) {
|
||||
dataForm.frontendPath = frontendPath;
|
||||
dataForm.backendPath = backendPath;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (frontendPath && backendPath) {
|
||||
dataForm.frontendPath = frontendPath;
|
||||
dataForm.backendPath = backendPath;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const dataRules = ref({
|
||||
tableName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
tableComment: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
className: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
packageName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
author: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
moduleName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
functionName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
generatorType: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
formLayout: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
backendPath: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
frontendPath: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
style: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
tableName: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
tableComment: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
className: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
packageName: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
author: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
moduleName: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
functionName: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
generatorType: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
formLayout: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
backendPath: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
frontendPath: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
style: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
|
||||
});
|
||||
|
||||
// 保存
|
||||
const submitHandle = async () => {
|
||||
try {
|
||||
await dataFormRef.value.validate();
|
||||
loading.value = true;
|
||||
await putObj(Object.assign(dataForm, childForm.value));
|
||||
visible.value = false;
|
||||
emit('refreshDataList');
|
||||
return dataForm;
|
||||
} catch {
|
||||
return Promise.reject();
|
||||
} finally {
|
||||
//保存路径至Local 中方便下次使用
|
||||
if (dataForm.generatorType === 1) {
|
||||
Local.set('frontendPath', dataForm.frontendPath);
|
||||
Local.set('backendPath', dataForm.backendPath);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
try {
|
||||
await dataFormRef.value.validate();
|
||||
loading.value = true;
|
||||
await putObj(Object.assign(dataForm, childForm.value));
|
||||
visible.value = false;
|
||||
emit('refreshDataList');
|
||||
return dataForm;
|
||||
} catch {
|
||||
return Promise.reject();
|
||||
} finally {
|
||||
//保存路径至Local 中方便下次使用
|
||||
if (dataForm.generatorType === 1) {
|
||||
Local.set('frontendPath', dataForm.frontendPath);
|
||||
Local.set('backendPath', dataForm.backendPath);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const genGroupList = () => {
|
||||
groupList().then((res) => {
|
||||
groupDataList.value = res.data;
|
||||
});
|
||||
groupList().then((res) => {
|
||||
groupDataList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => childForm,
|
||||
() => {
|
||||
const { childTableName } = childForm.value || {};
|
||||
tableNameStr.value = childTableName ? `${props.tableName} + ${childTableName}` : props.tableName;
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
dataForm.id = '';
|
||||
dataForm.tableName = String(props.tableName);
|
||||
dataForm.dsName = String(props.dsName);
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
dataForm.id = '';
|
||||
dataForm.tableName = String(props.tableName);
|
||||
dataForm.dsName = String(props.dsName);
|
||||
|
||||
getTable(dataForm.dsName, dataForm.tableName);
|
||||
genGroupList();
|
||||
getTable(dataForm.dsName, dataForm.tableName);
|
||||
genGroupList();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
submitHandle,
|
||||
submitHandle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.generator-code .el-dialog__body {
|
||||
padding: 15px 30px 0 20px;
|
||||
padding: 15px 30px 0 20px;
|
||||
}
|
||||
</style>
|
||||
|
@ -44,13 +44,12 @@
|
||||
<el-table-column :label="t('table.tableName')" prop="tableName" show-overflow-tooltip />
|
||||
<el-table-column :label="t('table.tableDesc')" prop="tableComment" show-overflow-tooltip />
|
||||
<el-table-column :label="t('table.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<el-table-column :label="$t('common.action')" width="200">
|
||||
<template #default="scope">
|
||||
<el-button icon="Refresh" @click="syncTable(scope.row)" text type="primary">
|
||||
{{ $t('gen.syncBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="FolderOpened" @click="openGen(scope.row)" text type="primary">{{ $t('gen.genBtn') }} </el-button>
|
||||
<el-button icon="MagicStick" @click="openDesign(scope.row)" text type="primary">{{ $t('gen.designBtn') }} </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -116,16 +115,6 @@ const openGen = (row) => {
|
||||
});
|
||||
};
|
||||
|
||||
const openDesign = (row) => {
|
||||
router.push({
|
||||
path: '/gen/design/index',
|
||||
query: {
|
||||
tableName: row.tableName,
|
||||
dsName: state.queryForm.dsName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 同步表数据
|
||||
const syncTable = (row) => {
|
||||
useSyncTableApi(state.queryForm.dsName, row.tableName).then(() => {
|
||||
|
@ -1,47 +1,35 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<splitpanes horizontal>
|
||||
<pane size="60">
|
||||
<splitpanes>
|
||||
<pane size="67">
|
||||
<splitpanes horizontal>
|
||||
<pane size="30">
|
||||
<current-user />
|
||||
</pane>
|
||||
<pane size="70">
|
||||
<favorite />
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
<pane size="33">
|
||||
<schedule-calendar />
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
<pane size="40">
|
||||
<splitpanes>
|
||||
<pane>
|
||||
<sys-log />
|
||||
</pane>
|
||||
<pane>
|
||||
<audit-log />
|
||||
</pane>
|
||||
<pane>
|
||||
<news-letter />
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<splitpanes>
|
||||
<pane size="70">
|
||||
<splitpanes horizontal>
|
||||
<pane size="25">
|
||||
<current-user/>
|
||||
</pane>
|
||||
<pane size="75">
|
||||
<favorite/>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
<pane size="30">
|
||||
<splitpanes horizontal>
|
||||
<pane size="58">
|
||||
<schedule/>
|
||||
</pane>
|
||||
<pane size="42">
|
||||
<sys-log/>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="home">
|
||||
const CurrentUser = defineAsyncComponent(() => import('./current-user.vue'));
|
||||
const Favorite = defineAsyncComponent(() => import('./favorite.vue'));
|
||||
const ScheduleCalendar = defineAsyncComponent(() => import('./schedule/calendar.vue'));
|
||||
const SysLog = defineAsyncComponent(() => import('./log-dashboard/sys-log.vue'));
|
||||
const AuditLog = defineAsyncComponent(() => import('./log-dashboard/audit-log.vue'));
|
||||
const NewsLetter = defineAsyncComponent(() => import('./newsletter.vue'));
|
||||
const Schedule = defineAsyncComponent(() => import('./schedule.vue'));
|
||||
const SysLog = defineAsyncComponent(() => import('./sys-log.vue'));
|
||||
</script>
|
||||
|
@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<el-card class="box-card" style="height: 100%">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('home.auditLogsTip') }}</span>
|
||||
<el-button link class="button" text @click="handleRoutr">{{ $t('home.moreTip') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-timeline v-if="auditState.dataList.length > 0">
|
||||
<el-timeline-item v-for="(item, index) in auditState.dataList" :key="index" :timestamp="item.createTime">
|
||||
{{ item.createBy }} : {{ item.auditField }} {{ item.afterVal }} => {{ item.beforeVal }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<el-empty v-else />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SysAuditLogDashboard">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList } from '/@/api/admin/audit';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 创建基本表格参数对象
|
||||
const auditState: BasicTableProps = reactive({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// 使用实例
|
||||
useTable(auditState);
|
||||
|
||||
// 跳转路由
|
||||
const handleRoutr = () => {
|
||||
router.push('/admin/audit/index');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('home.newsletterTip') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-timeline v-if="newsList.length > 0">
|
||||
<el-timeline-item v-for="(item, index) in newsList.slice(0, 5)" :key="index" :timestamp="item.time">
|
||||
{{ item.label }} - {{ item.value }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<el-empty v-else />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="newsLetter">
|
||||
import { useMsg } from '/@/stores/msg';
|
||||
|
||||
/**
|
||||
* 获取消息列表 store 对象的实例。
|
||||
*/
|
||||
const mes = useMsg();
|
||||
|
||||
/**
|
||||
* 消息列表的计算属性。
|
||||
*/
|
||||
const newsList = computed(() => {
|
||||
return mes.getAllMsg(); // 从 store 中获取所有消息
|
||||
});
|
||||
</script>
|
12
src/views/home/schedule.vue
Normal file
12
src/views/home/schedule.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<el-calendar v-model="value"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysSchedule">
|
||||
const value = ref(new Date())
|
||||
</script>
|
||||
<style>
|
||||
.el-calendar-table .el-calendar-day{
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<el-calendar v-model="calendar">
|
||||
<template #date-cell="{ data }">
|
||||
<div class="calendar-cell" @click="handleSchedule(data)">
|
||||
{{ data.day.split('-').slice(2).join('-') }}
|
||||
<div class="box-yello" v-if="filterCellSelected(data)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
<!-- 新增日程的表单 -->
|
||||
<schedule-form ref="scheduleFormRef" @refresh="initscheduleList(formatDate(calendar, 'YYYY-mm'))" />
|
||||
<!-- 日程查询 -->
|
||||
<schedule ref="scheduleRef" @refresh="initscheduleList(formatDate(calendar, 'YYYY-mm'))" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SysScheduleCalendar">
|
||||
import { formatDate, parseDate } from '/@/utils/formatTime';
|
||||
import { list } from '/@/api/admin/schedule';
|
||||
|
||||
const ScheduleForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const Schedule = defineAsyncComponent(() => import('./index.vue'));
|
||||
const scheduleDataList = ref([]);
|
||||
const scheduleFormRef = ref();
|
||||
const scheduleRef = ref();
|
||||
const calendar = ref(new Date());
|
||||
|
||||
onMounted(() => {
|
||||
initscheduleList(formatDate(calendar.value, 'YYYY-mm'));
|
||||
});
|
||||
|
||||
// 监听日历日期变化并更新日程数据
|
||||
watch(calendar, (val, oldval) => {
|
||||
const newVal = formatDate(val, 'YYYY-mm');
|
||||
const old = formatDate(oldval, 'YYYY-mm');
|
||||
if (newVal !== old) {
|
||||
initscheduleList(newVal);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 初始化日程数据列表
|
||||
* @function
|
||||
* @param {string} date - YYYY-mm格式的日期字符串
|
||||
*/
|
||||
const initscheduleList = (date) => {
|
||||
list({
|
||||
month: `${date}-01`,
|
||||
}).then((res) => {
|
||||
scheduleDataList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 过滤日历中选中的单元格是否有日程
|
||||
* @function
|
||||
* @param {object} data - 当前单元格包含的日期信息
|
||||
* @returns {boolean} 是否有日程
|
||||
*/
|
||||
const filterCellSelected = (data) => {
|
||||
return (
|
||||
scheduleDataList.value.filter((item) => {
|
||||
return item.date.indexOf(data.day) >= 0;
|
||||
}).length > 0
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理日程安排事件,若当前日期下没有日程则打开表单对话框,否则打开日程详情页面
|
||||
* @function
|
||||
* @param {object} data - 当前单元格包含的日期信息
|
||||
*/
|
||||
const handleSchedule = (data) => {
|
||||
if (filterCellSelected(data)) {
|
||||
scheduleRef.value.open({ date: parseDate(data.date, null) });
|
||||
} else {
|
||||
scheduleFormRef.value.openDialog(null, { date: parseDate(data.date, null) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-calendar-table {
|
||||
.calendar-cell {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.box-yello {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #e6a23c;
|
||||
opacity: 0.6;
|
||||
padding: 0;
|
||||
border-radius: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false">
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.title')" prop="title">
|
||||
<el-input v-model="form.title" :placeholder="t('schedule.inputTitleTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.type')" prop="type">
|
||||
<el-select v-model="form.type" :placeholder="t('schedule.inputTypeTip')" clearable class="w100" default-first-option>
|
||||
<el-option v-for="item in schedule_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.state')" prop="state">
|
||||
<el-select v-model="form.state" :placeholder="t('schedule.inputStateTip')" clearable class="w100" default-first-option>
|
||||
<el-option v-for="item in schedule_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('schedule.date')" prop="date">
|
||||
<el-date-picker type="date" :placeholder="t('schedule.inputDateTip')" v-model="form.date" :value-format="dateStr"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('schedule.time')" prop="time">
|
||||
<el-time-picker v-model="form.time" arrow-control :placeholder="t('schedule.inputTimeTip')" :value-format="timeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('schedule.content')" prop="content">
|
||||
<editor v-model:get-html="form.content" :placeholder="t('schedule.inputContentTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SysScheduleDialog">
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj } from '/@/api/admin/schedule';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 获取字典内容
|
||||
const { schedule_type, schedule_status } = useDict('schedule_type', 'schedule_status');
|
||||
|
||||
// 使用国际化实例
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
title: '',
|
||||
type: 'record',
|
||||
state: '0',
|
||||
content: '',
|
||||
time: '',
|
||||
date: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '日程类型不能为空', trigger: 'blur' }],
|
||||
state: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
|
||||
time: [{ required: true, message: '时间不能为空', trigger: 'blur' }],
|
||||
date: [{ required: true, message: '日期不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开日程表单弹窗
|
||||
* @function
|
||||
* @param {string} id - 日程ID
|
||||
* @param {Object} row - 行数据
|
||||
*/
|
||||
const openDialog = (id: string, row: any) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => dataFormRef.value?.resetFields());
|
||||
|
||||
if (row?.date) {
|
||||
form.date = row.date;
|
||||
}
|
||||
|
||||
// 获取sysSchedule信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getsysScheduleData(id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单数据
|
||||
* @function
|
||||
* @async
|
||||
*/
|
||||
const onSubmit = async () => {
|
||||
// 验证表单是否符合规则
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
await (form.id ? putObj(form) : addObj(form));
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取sysSchedule数据
|
||||
* @function
|
||||
* @param {string} id - 日程ID
|
||||
*/
|
||||
const getsysScheduleData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true;
|
||||
getObj(id)
|
||||
.then((res: any) => Object.assign(form, res.data))
|
||||
.finally(() => (loading.value = false));
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({ openDialog });
|
||||
</script>
|
@ -1,170 +0,0 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="日程管理" size="80%" @close="handleClose">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item :label="t('schedule.date')" prop="date">
|
||||
<el-date-picker
|
||||
type="date"
|
||||
:placeholder="t('schedule.inputDateTip')"
|
||||
v-model="state.queryForm.date"
|
||||
:value-format="dateStr"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('schedule.title')" prop="title">
|
||||
<el-input :placeholder="t('schedule.inputTitleTip')" v-model="state.queryForm.title" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" formDialogRef @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button formDialogRef icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog(null, state.queryForm)">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button formDialogRef :disabled="multiple" icon="Delete" type="primary" class="ml10" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="true"
|
||||
@exportExcel="exportExcel"
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" :label="t('schedule.index')" width="80" />
|
||||
<el-table-column prop="title" :label="t('schedule.title')" show-overflow-tooltip />
|
||||
<el-table-column prop="type" :label="t('schedule.type')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="schedule_type" :value="scope.row.type"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="state" :label="t('schedule.state')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="schedule_status" :value="scope.row.state"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" :label="t('schedule.date')" show-overflow-tooltip />
|
||||
<el-table-column prop="time" :label="t('schedule.time')" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" :label="t('schedule.createBy')" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" :label="t('schedule.createTime')" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog(scope.row.id)">{{ $t('common.editBtn') }}</el-button>
|
||||
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">{{ $t('common.delBtn') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysSchedule">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/admin/schedule';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import DictTag from '/@/components/DictTag/index.vue';
|
||||
const { schedule_type, schedule_status } = useDict('schedule_type', 'schedule_status');
|
||||
const emit = defineEmits(['refresh']);
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const visible = ref(false);
|
||||
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
createdIsNeed: false,
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
state.queryForm.date = '';
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/job/schedule/export', state.queryForm, 'schedule.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
//关闭日程刷新首页日程数据
|
||||
const handleClose = () => {
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const open = (row: any) => {
|
||||
state.queryForm.date = row.date;
|
||||
getDataList();
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
@ -25,7 +25,7 @@ const logState: BasicTableProps = reactive({
|
||||
pageList, // 分页列表数据
|
||||
descs: ['create_time'], // 排序方式
|
||||
pagination: {
|
||||
size: 5, // 每页显示数据量
|
||||
size: 3, // 每页显示数据量
|
||||
},
|
||||
});
|
||||
|
@ -9,7 +9,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation2" prop="code">
|
||||
<el-col :span="15">
|
||||
<el-input text maxlength="4" :placeholder="$t('mobile.placeholder2')" v-model="loginForm.code" clearable autocomplete="off">
|
||||
<el-input text maxlength="6" :placeholder="$t('mobile.placeholder2')" v-model="loginForm.code" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon">
|
||||
<ele-Position />
|
||||
|
@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<div class="select-none">
|
||||
<div class="mini_qr">
|
||||
<!-- 扫码体验移动端 -->
|
||||
<img :src="miniQr" />
|
||||
<p>{{ t('scan.wechatApp') }}</p>
|
||||
</div>
|
||||
<img :src="bg" class="wave" />
|
||||
<div class="flex-c absolute right-5 top-3"></div>
|
||||
<div class="login-container">
|
||||
|
@ -1546,11 +1546,6 @@ form-data@^4.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-designer-plus@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/form-designer-plus/-/form-designer-plus-0.1.5.tgz#5b1dacf17e91dc3f7bde05206c678c039334d286"
|
||||
integrity sha512-PRiwfwTPHxzFYPtXhGG5IE4KezM4orNuruaGBWVK+qh8BE3UaTM2glL99Fiy3f8a4lEyxpiQH67KIB0j/T5xiw==
|
||||
|
||||
fraction.js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
|
Loading…
Reference in New Issue
Block a user