🔖 Releasing / Version tags. 3.7.0-snapshot

This commit is contained in:
lbw 2023-07-07 16:14:05 +08:00
parent 7bee6535ea
commit 2ef280281c
64 changed files with 890 additions and 3625 deletions

115
README.md Normal file
View 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>

View File

@ -1,6 +1,6 @@
version: '3'
services:
pigx-ui:
pig-ui:
build:
context: .
restart: always

View File

@ -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;

View File

@ -18,7 +18,7 @@
<!--避免微信管理防盗链机制-->
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="/favicon.ico" />
<title>PIGX 微服务快速开发平台</title>
<title>PIG 微服务快速开发平台</title>
</head>
<body>

View File

@ -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",

View File

@ -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,
});
}

View File

@ -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',
});
};

View File

@ -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,
});
}

View File

@ -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,
});
}

View File

@ -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',
});
}

View File

@ -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();
}
});
}

View File

@ -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',
});
};

View File

@ -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);
}
};
//

View File

@ -97,7 +97,7 @@ const getThemeConfig = computed(() => {
// tagsView tagsView
const setTagsViewNameI18n = computed(() => {
return (v: RouteItem) => {
return other.setTagsViewNameI18n(v);
return other.setMenuI18n(v);
};
});
// tagsView

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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');

View File

@ -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

View File

@ -223,7 +223,7 @@ body,
}
.flex-center {
@extend .flex;
flex-direction: column;
flex-direction: column !important;
width: 100%;
overflow: hidden;
}

View File

@ -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 queryparams 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;
};
// 统一批量导出

View File

@ -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);

View File

@ -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();
}
};

View File

@ -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',
},
};

View File

@ -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',
},
};

View File

@ -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>

View File

@ -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' }],
});

View File

@ -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>

View File

@ -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',
},
};

View File

@ -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: '导入系统表-国际化',
},
};

View File

@ -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>

View File

@ -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: '',

View File

@ -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',

View File

@ -2,12 +2,14 @@ export default {
sysmenu: {
index: '#',
name: '菜单名称',
enName: '菜单英文',
sortOrder: '排序',
path: '路由',
menuType: '类型',
keepAlive: '缓冲',
permission: '权限标识',
inputNameTip: '请输入菜单名称',
inputEnNameTip: '请输入菜单英文',
parentId: '上级菜单',
embedded: '是否内嵌',
visible: '是否显示',

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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',
},
};

View File

@ -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: '请输入所属租户',
},
};

View File

@ -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>

View File

@ -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>

View File

@ -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',
},
};

View File

@ -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: '创建',
},
};

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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(() => {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -25,7 +25,7 @@ const logState: BasicTableProps = reactive({
pageList, //
descs: ['create_time'], //
pagination: {
size: 5, //
size: 3, //
},
});

View File

@ -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 />

View File

@ -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">

View File

@ -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"