添加对ele的支持,目前vben支持还不是特别完善
This commit is contained in:
@@ -4,10 +4,10 @@ VITE_PORT=5777
|
|||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_GLOB_API_URL=/api
|
VITE_GLOB_API_URL=http://127.0.0.1:8000/api
|
||||||
|
|
||||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||||
VITE_NITRO_MOCK=true
|
VITE_NITRO_MOCK=false
|
||||||
|
|
||||||
# 是否打开 devtools,true 为打开,false 为关闭
|
# 是否打开 devtools,true 为打开,false 为关闭
|
||||||
VITE_DEVTOOLS=false
|
VITE_DEVTOOLS=false
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
VITE_GLOB_API_URL=http://127.0.0.1:8000/api
|
||||||
|
|
||||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||||
VITE_COMPRESS=none
|
VITE_COMPRESS=gzip
|
||||||
|
|
||||||
# 是否开启 PWA
|
# 是否开启 PWA
|
||||||
VITE_PWA=false
|
VITE_PWA=false
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
|||||||
import { ElButton, ElImage } from 'element-plus';
|
import { ElButton, ElImage } from 'element-plus';
|
||||||
|
|
||||||
import { useVbenForm } from './form';
|
import { useVbenForm } from './form';
|
||||||
|
import type {Recordable} from "@vben-core/typings";
|
||||||
|
|
||||||
setupVbenVxeTable({
|
setupVbenVxeTable({
|
||||||
configVxeTable: (vxeUI) => {
|
configVxeTable: (vxeUI) => {
|
||||||
@@ -66,5 +67,11 @@ setupVbenVxeTable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export { useVbenVxeGrid };
|
export { useVbenVxeGrid };
|
||||||
|
export type OnActionClickParams<T = Recordable<any>> = {
|
||||||
|
code: string;
|
||||||
|
row: T;
|
||||||
|
};
|
||||||
|
export type OnActionClickFn<T = Recordable<any>> = (
|
||||||
|
params: OnActionClickParams<T>,
|
||||||
|
) => void;
|
||||||
export type * from '@vben/plugins/vxe-table';
|
export type * from '@vben/plugins/vxe-table';
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ export namespace AuthApi {
|
|||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>('/system/login/', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新accessToken
|
* 刷新accessToken
|
||||||
*/
|
*/
|
||||||
export async function refreshTokenApi() {
|
export async function refreshTokenApi() {
|
||||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/system/refresh/', {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export async function refreshTokenApi() {
|
|||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
export async function logoutApi() {
|
export async function logoutApi() {
|
||||||
return baseRequestClient.post('/auth/logout', {
|
return baseRequestClient.post('/system/logout/', {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -47,5 +47,5 @@ export async function logoutApi() {
|
|||||||
* 获取用户权限码
|
* 获取用户权限码
|
||||||
*/
|
*/
|
||||||
export async function getAccessCodesApi() {
|
export async function getAccessCodesApi() {
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
return requestClient.get<string[]>('/system/codes/');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ import { requestClient } from '#/api/request';
|
|||||||
* 获取用户所有菜单
|
* 获取用户所有菜单
|
||||||
*/
|
*/
|
||||||
export async function getAllMenusApi() {
|
export async function getAllMenusApi() {
|
||||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
return requestClient.get<RouteRecordStringComponent[]>(
|
||||||
|
'/system/menu/user_menu',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ import { requestClient } from '#/api/request';
|
|||||||
* 获取用户信息
|
* 获取用户信息
|
||||||
*/
|
*/
|
||||||
export async function getUserInfoApi() {
|
export async function getUserInfoApi() {
|
||||||
return requestClient.get<UserInfo>('/user/info');
|
return requestClient.get<UserInfo>('/system/info/');
|
||||||
}
|
}
|
||||||
|
|||||||
56
web/apps/web-ele/src/api/system/dept.ts
Normal file
56
web/apps/web-ele/src/api/system/dept.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemDeptApi {
|
||||||
|
export interface SystemDept {
|
||||||
|
[key: string]: any;
|
||||||
|
children?: SystemDept[];
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
remark?: string;
|
||||||
|
status: 0 | 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部门列表数据
|
||||||
|
*/
|
||||||
|
async function getDeptList(params?: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemDeptApi.SystemDept>>('/system/dept/', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建部门
|
||||||
|
* @param data 部门数据
|
||||||
|
*/
|
||||||
|
async function createDept(
|
||||||
|
data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/system/dept/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新部门
|
||||||
|
*
|
||||||
|
* @param id 部门 ID
|
||||||
|
* @param data 部门数据
|
||||||
|
*/
|
||||||
|
async function updateDept(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/dept/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除部门
|
||||||
|
* @param id 部门 ID
|
||||||
|
*/
|
||||||
|
async function deleteDept(id: string) {
|
||||||
|
return requestClient.delete(`/system/dept/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createDept, deleteDept, getDeptList, updateDept };
|
||||||
74
web/apps/web-ele/src/api/system/dict_data.ts
Normal file
74
web/apps/web-ele/src/api/system/dict_data.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemDictDataApi {
|
||||||
|
export interface SystemDictData {
|
||||||
|
[key: string]: any;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典数据列表数据
|
||||||
|
*/
|
||||||
|
async function getDictDataList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemDictDataApi.SystemDictData>>(
|
||||||
|
'/system/dict_data/',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建字典数据
|
||||||
|
* @param data 字典数据数据
|
||||||
|
*/
|
||||||
|
async function createDictData(
|
||||||
|
data: Omit<SystemDictDataApi.SystemDictData, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/system/dict_data/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典数据
|
||||||
|
*
|
||||||
|
* @param id 字典数据 ID
|
||||||
|
* @param data 字典数据数据
|
||||||
|
*/
|
||||||
|
async function updateDictData(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemDictDataApi.SystemDictData, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/dict_data/${id}/`, data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新字典数据
|
||||||
|
*
|
||||||
|
* @param id 字典数据 ID
|
||||||
|
* @param data 字典数据数据
|
||||||
|
*/
|
||||||
|
async function patchDictData(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemDictDataApi.SystemDictData, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.patch(`/system/dict_data/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典数据
|
||||||
|
* @param id 字典数据 ID
|
||||||
|
*/
|
||||||
|
async function deleteDictData(id: string) {
|
||||||
|
return requestClient.delete(`/system/dict_data/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createDictData,
|
||||||
|
deleteDictData,
|
||||||
|
getDictDataList,
|
||||||
|
patchDictData,
|
||||||
|
updateDictData,
|
||||||
|
};
|
||||||
75
web/apps/web-ele/src/api/system/dict_type.ts
Normal file
75
web/apps/web-ele/src/api/system/dict_type.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemDictTypeApi {
|
||||||
|
export interface SystemDictType {
|
||||||
|
[key: string]: any;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典类型列表数据
|
||||||
|
*/
|
||||||
|
async function getDictTypeList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemDictTypeApi.SystemDictType>>(
|
||||||
|
'/system/dict_type/',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建字典类型
|
||||||
|
* @param data 字典类型数据
|
||||||
|
*/
|
||||||
|
async function createDictType(
|
||||||
|
data: Omit<SystemDictTypeApi.SystemDictType, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/system/dict_type/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典类型
|
||||||
|
*
|
||||||
|
* @param id 字典类型 ID
|
||||||
|
* @param data 字典类型数据
|
||||||
|
*/
|
||||||
|
async function updateDictType(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemDictTypeApi.SystemDictType, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/dict_type/${id}/`, data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新字典类型
|
||||||
|
*
|
||||||
|
* @param id 字典类型 ID
|
||||||
|
* @param data 字典类型数据
|
||||||
|
*/
|
||||||
|
async function patchDictType(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemDictTypeApi.SystemDictType, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.patch(`/system/dict_type/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典类型
|
||||||
|
* @param id 字典类型 ID
|
||||||
|
*/
|
||||||
|
async function deleteDictType(id: string) {
|
||||||
|
return requestClient.delete(`/system/dict_type/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createDictType,
|
||||||
|
deleteDictType,
|
||||||
|
getDictTypeList,
|
||||||
|
patchDictType,
|
||||||
|
updateDictType,
|
||||||
|
};
|
||||||
3
web/apps/web-ele/src/api/system/index.ts
Normal file
3
web/apps/web-ele/src/api/system/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './dept';
|
||||||
|
export * from './menu';
|
||||||
|
export * from './role';
|
||||||
168
web/apps/web-ele/src/api/system/menu.ts
Normal file
168
web/apps/web-ele/src/api/system/menu.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemMenuApi {
|
||||||
|
/** 徽标颜色集合 */
|
||||||
|
export const BadgeVariants = [
|
||||||
|
'default',
|
||||||
|
'destructive',
|
||||||
|
'primary',
|
||||||
|
'success',
|
||||||
|
'warning',
|
||||||
|
] as const;
|
||||||
|
/** 徽标类型集合 */
|
||||||
|
export const BadgeTypes = ['dot', 'normal'] as const;
|
||||||
|
/** 菜单类型集合 */
|
||||||
|
export const MenuTypes = [
|
||||||
|
'catalog',
|
||||||
|
'menu',
|
||||||
|
'embedded',
|
||||||
|
'link',
|
||||||
|
'button',
|
||||||
|
] as const;
|
||||||
|
/** 系统菜单 */
|
||||||
|
export interface SystemMenu {
|
||||||
|
[key: string]: any;
|
||||||
|
/** 后端权限标识 */
|
||||||
|
auth_code: string;
|
||||||
|
/** 子级 */
|
||||||
|
children?: SystemMenu[];
|
||||||
|
/** 组件 */
|
||||||
|
component?: string;
|
||||||
|
/** 菜单ID */
|
||||||
|
id: string;
|
||||||
|
/** 菜单元数据 */
|
||||||
|
meta?: {
|
||||||
|
/** 激活时显示的图标 */
|
||||||
|
activeIcon?: string;
|
||||||
|
/** 作为路由时,需要激活的菜单的Path */
|
||||||
|
activePath?: string;
|
||||||
|
/** 固定在标签栏 */
|
||||||
|
affixTab?: boolean;
|
||||||
|
/** 在标签栏固定的顺序 */
|
||||||
|
affixTabOrder?: number;
|
||||||
|
/** 徽标内容(当徽标类型为normal时有效) */
|
||||||
|
badge?: string;
|
||||||
|
/** 徽标类型 */
|
||||||
|
badgeType?: (typeof BadgeTypes)[number];
|
||||||
|
/** 徽标颜色 */
|
||||||
|
badgeVariants?: (typeof BadgeVariants)[number];
|
||||||
|
/** 在菜单中隐藏下级 */
|
||||||
|
hideChildrenInMenu?: boolean;
|
||||||
|
/** 在面包屑中隐藏 */
|
||||||
|
hideInBreadcrumb?: boolean;
|
||||||
|
/** 在菜单中隐藏 */
|
||||||
|
hideInMenu?: boolean;
|
||||||
|
/** 在标签栏中隐藏 */
|
||||||
|
hideInTab?: boolean;
|
||||||
|
/** 菜单图标 */
|
||||||
|
icon?: string;
|
||||||
|
/** 内嵌Iframe的URL */
|
||||||
|
iframeSrc?: string;
|
||||||
|
/** 是否缓存页面 */
|
||||||
|
keepAlive?: boolean;
|
||||||
|
/** 外链页面的URL */
|
||||||
|
link?: string;
|
||||||
|
/** 同一个路由最大打开的标签数 */
|
||||||
|
maxNumOfOpenTab?: number;
|
||||||
|
/** 无需基础布局 */
|
||||||
|
noBasicLayout?: boolean;
|
||||||
|
/** 是否在新窗口打开 */
|
||||||
|
openInNewWindow?: boolean;
|
||||||
|
/** 菜单排序 */
|
||||||
|
order?: number;
|
||||||
|
/** 额外的路由参数 */
|
||||||
|
query?: Recordable<any>;
|
||||||
|
/** 菜单标题 */
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
/** 菜单名称 */
|
||||||
|
name: string;
|
||||||
|
/** 路由路径 */
|
||||||
|
path: string;
|
||||||
|
/** 父级ID */
|
||||||
|
pid: string;
|
||||||
|
/** 重定向 */
|
||||||
|
redirect?: string;
|
||||||
|
/** 菜单类型 */
|
||||||
|
type: (typeof MenuTypes)[number];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单数据列表
|
||||||
|
*/
|
||||||
|
async function getMenuList() {
|
||||||
|
return requestClient.get<Array<SystemMenuApi.SystemMenu>>('/system/menu/');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isMenuNameExists(
|
||||||
|
name: string,
|
||||||
|
id?: SystemMenuApi.SystemMenu['id'],
|
||||||
|
) {
|
||||||
|
const url = id ? `/system/menu/${id}/` : `/system/menu/`;
|
||||||
|
return requestClient.get<boolean>(url, {
|
||||||
|
params: { id, name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isMenuSearchExists(
|
||||||
|
name: string,
|
||||||
|
id?: SystemMenuApi.SystemMenu['id'],
|
||||||
|
pid?: SystemMenuApi.SystemMenu['pid'],
|
||||||
|
) {
|
||||||
|
return requestClient.get<boolean>('/system/menu/name-search', {
|
||||||
|
params: { name, id, pid },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isMenuPathExists(
|
||||||
|
path: string,
|
||||||
|
id?: SystemMenuApi.SystemMenu['id'],
|
||||||
|
) {
|
||||||
|
return requestClient.get<boolean>('/system/menu/path-exists', {
|
||||||
|
params: { id, path },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建菜单
|
||||||
|
* @param data 菜单数据
|
||||||
|
*/
|
||||||
|
async function createMenu(
|
||||||
|
data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/system/menu/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新菜单
|
||||||
|
*
|
||||||
|
* @param id 菜单 ID
|
||||||
|
* @param data 菜单数据
|
||||||
|
*/
|
||||||
|
async function updateMenu(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/menu/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除菜单
|
||||||
|
* @param id 菜单 ID
|
||||||
|
*/
|
||||||
|
async function deleteMenu(id: string) {
|
||||||
|
return requestClient.delete(`/system/menu/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createMenu,
|
||||||
|
deleteMenu,
|
||||||
|
getMenuList,
|
||||||
|
isMenuNameExists,
|
||||||
|
isMenuPathExists,
|
||||||
|
isMenuSearchExists,
|
||||||
|
updateMenu,
|
||||||
|
};
|
||||||
70
web/apps/web-ele/src/api/system/role.ts
Normal file
70
web/apps/web-ele/src/api/system/role.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemRoleApi {
|
||||||
|
export interface SystemRole {
|
||||||
|
[key: string]: any;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
permissions: [];
|
||||||
|
profile: {
|
||||||
|
create_time: string;
|
||||||
|
permissions: [];
|
||||||
|
remark?: string;
|
||||||
|
status: 0 | 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色列表数据
|
||||||
|
*/
|
||||||
|
async function getRoleList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemRoleApi.SystemRole>>('/system/role/', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建角色
|
||||||
|
* @param data 角色数据
|
||||||
|
*/
|
||||||
|
async function createRole(data: Omit<SystemRoleApi.SystemRole, 'id'>) {
|
||||||
|
return requestClient.post('/system/role/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新角色
|
||||||
|
*
|
||||||
|
* @param id 角色 ID
|
||||||
|
* @param data 角色数据
|
||||||
|
*/
|
||||||
|
async function updateRole(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemRoleApi.SystemRole, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/role/${id}/`, data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新角色
|
||||||
|
*
|
||||||
|
* @param id 角色 ID
|
||||||
|
* @param data 角色数据
|
||||||
|
*/
|
||||||
|
async function patchRole(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemRoleApi.SystemRole, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.patch(`/system/role/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除角色
|
||||||
|
* @param id 角色 ID
|
||||||
|
*/
|
||||||
|
async function deleteRole(id: string) {
|
||||||
|
return requestClient.delete(`/system/role/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createRole, deleteRole, getRoleList, patchRole, updateRole };
|
||||||
74
web/apps/web-ele/src/api/system/tenant_package.ts
Normal file
74
web/apps/web-ele/src/api/system/tenant_package.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemTenantPackageApi {
|
||||||
|
export interface SystemTenantPackage {
|
||||||
|
[key: string]: any;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取租户列表数据
|
||||||
|
*/
|
||||||
|
async function getTenantPackageList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemTenantPackageApi.SystemTenantPackage>>(
|
||||||
|
'/system/tenant_package/',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建租户
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function createTenantPackage(
|
||||||
|
data: Omit<SystemTenantPackageApi.SystemTenantPackage, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.post('/system/tenant_package/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新租户
|
||||||
|
*
|
||||||
|
* @param id 租户 ID
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function updateTenantPackage(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemTenantPackageApi.SystemTenantPackage, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/tenant_package/${id}/`, data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新租户
|
||||||
|
*
|
||||||
|
* @param id 租户 ID
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function patchTenantPackage(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemTenantPackageApi.SystemTenantPackage, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.patch(`/system/tenant_package/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除租户
|
||||||
|
* @param id 租户 ID
|
||||||
|
*/
|
||||||
|
async function deleteTenantPackage(id: string) {
|
||||||
|
return requestClient.delete(`/system/tenant_package/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createTenantPackage,
|
||||||
|
deleteTenantPackage,
|
||||||
|
getTenantPackageList,
|
||||||
|
patchTenantPackage,
|
||||||
|
updateTenantPackage,
|
||||||
|
};
|
||||||
71
web/apps/web-ele/src/api/system/tenants.ts
Normal file
71
web/apps/web-ele/src/api/system/tenants.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemTenantsApi {
|
||||||
|
export interface SystemTenants {
|
||||||
|
[key: string]: any;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取租户列表数据
|
||||||
|
*/
|
||||||
|
async function getTenantsList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<Array<SystemTenantsApi.SystemTenants>>(
|
||||||
|
'/system/tenants/',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建租户
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function createTenants(data: Omit<SystemTenantsApi.SystemTenants, 'id'>) {
|
||||||
|
return requestClient.post('/system/tenants/', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新租户
|
||||||
|
*
|
||||||
|
* @param id 租户 ID
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function updateTenants(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemTenantsApi.SystemTenants, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.put(`/system/tenants/${id}/`, data);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新租户
|
||||||
|
*
|
||||||
|
* @param id 租户 ID
|
||||||
|
* @param data 租户数据
|
||||||
|
*/
|
||||||
|
async function patchTenants(
|
||||||
|
id: string,
|
||||||
|
data: Omit<SystemTenantsApi.SystemTenants, 'id'>,
|
||||||
|
) {
|
||||||
|
return requestClient.patch(`/system/tenants/${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除租户
|
||||||
|
* @param id 租户 ID
|
||||||
|
*/
|
||||||
|
async function deleteTenants(id: string) {
|
||||||
|
return requestClient.delete(`/system/tenants/${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createTenants,
|
||||||
|
deleteTenants,
|
||||||
|
getTenantsList,
|
||||||
|
patchTenants,
|
||||||
|
updateTenants,
|
||||||
|
};
|
||||||
@@ -6,11 +6,13 @@ import { preferences } from '@vben/preferences';
|
|||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/ele';
|
import '@vben/styles/ele';
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
import { ElLoading } from 'element-plus';
|
import { ElLoading } from 'element-plus';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
import { registerPermissionDirective } from '#/utils/permission';
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import { initSetupVbenForm } from './adapter/form';
|
import { initSetupVbenForm } from './adapter/form';
|
||||||
@@ -51,7 +53,8 @@ async function bootstrap(namespace: string) {
|
|||||||
|
|
||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
// 注册自定义v-permission指令
|
||||||
|
registerPermissionDirective(app);
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
@@ -62,6 +65,7 @@ async function bootstrap(namespace: string) {
|
|||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
app.use(ElementPlus);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
|||||||
113
web/apps/web-ele/src/locales/langs/en-US/system.json
Normal file
113
web/apps/web-ele/src/locales/langs/en-US/system.json
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"title": "System Management",
|
||||||
|
"dept": {
|
||||||
|
"name": "Department",
|
||||||
|
"title": "Department Management",
|
||||||
|
"deptName": "Department Name",
|
||||||
|
"status": "Status",
|
||||||
|
"createTime": "Created At",
|
||||||
|
"remark": "Remarks",
|
||||||
|
"operation": "Actions",
|
||||||
|
"parentDept": "Parent Department"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"title": "Menu Management",
|
||||||
|
"parent": "Parent Menu",
|
||||||
|
"menuTitle": "Menu Title",
|
||||||
|
"menuName": "Menu Name",
|
||||||
|
"name": "Menu",
|
||||||
|
"type": "Menu Type",
|
||||||
|
"typeCatalog": "Catalog",
|
||||||
|
"typeMenu": "Menu",
|
||||||
|
"typeButton": "Button",
|
||||||
|
"typeLink": "External Link",
|
||||||
|
"typeEmbedded": "Embedded Page",
|
||||||
|
"icon": "Icon",
|
||||||
|
"activeIcon": "Active Icon",
|
||||||
|
"activePath": "Active Path",
|
||||||
|
"path": "Route Path",
|
||||||
|
"component": "Component",
|
||||||
|
"status": "Status",
|
||||||
|
"sort": "sort",
|
||||||
|
"auth_code": "Permission Code",
|
||||||
|
"badge": "Badge",
|
||||||
|
"operation": "Actions",
|
||||||
|
"linkSrc": "Link URL",
|
||||||
|
"affixTab": "Pin Tab",
|
||||||
|
"keepAlive": "Keep Alive",
|
||||||
|
"hideInMenu": "Hide in Menu",
|
||||||
|
"hideInTab": "Hide in Tab Bar",
|
||||||
|
"hideChildrenInMenu": "Hide Children in Menu",
|
||||||
|
"hideInBreadcrumb": "Hide in Breadcrumb",
|
||||||
|
"advancedSettings": "Advanced Settings",
|
||||||
|
"activePathMustExist": "The path must correspond to a valid menu",
|
||||||
|
"activePathHelp": "When navigating to this route, if it doesn’t appear in the menu, you must specify the menu path that should be activated.",
|
||||||
|
"badgeType": {
|
||||||
|
"title": "Badge Type",
|
||||||
|
"dot": "Dot",
|
||||||
|
"normal": "Text",
|
||||||
|
"none": "None"
|
||||||
|
},
|
||||||
|
"badgeVariants": "Badge Style"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"title": "Role Management",
|
||||||
|
"list": "Role List",
|
||||||
|
"name": "Role",
|
||||||
|
"roleName": "Role Name",
|
||||||
|
"id": "Role ID",
|
||||||
|
"status": "Status",
|
||||||
|
"remark": "Remarks",
|
||||||
|
"createTime": "Created At",
|
||||||
|
"operation": "Actions",
|
||||||
|
"permissions": "Permissions",
|
||||||
|
"setPermissions": "Configure Permissions"
|
||||||
|
},
|
||||||
|
"tenant": {
|
||||||
|
"name": "Tenant",
|
||||||
|
"contact_mobile": "Contact Mobile",
|
||||||
|
"website": "Website",
|
||||||
|
"package_id": "Package Name",
|
||||||
|
"expire_time": "Expiration Time",
|
||||||
|
"account_count": "Account Limit",
|
||||||
|
"tenantName": "Tenant Name"
|
||||||
|
},
|
||||||
|
"tenant_package": {
|
||||||
|
"name": "Tenant Package",
|
||||||
|
"contact_mobile": "Contact Mobile",
|
||||||
|
"website": "Website",
|
||||||
|
"package_id": "Package Name",
|
||||||
|
"expire_time": "Expiration Time",
|
||||||
|
"account_count": "Account Limit",
|
||||||
|
"tenantName": "Tenant Name"
|
||||||
|
},
|
||||||
|
"dict_type": {
|
||||||
|
"name": "Dictionary Name",
|
||||||
|
"title": "Dictionary Name",
|
||||||
|
"type": "Dictionary Type"
|
||||||
|
},
|
||||||
|
"dict_data": {
|
||||||
|
"name": "Dictionary Label",
|
||||||
|
"title": "Dictionary Data",
|
||||||
|
"type": "Dictionary Value"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"name": "Post",
|
||||||
|
"title": "Post Management"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "User",
|
||||||
|
"title": "User Management"
|
||||||
|
},
|
||||||
|
"login_log": {
|
||||||
|
"name": "login log",
|
||||||
|
"title": "login log"
|
||||||
|
},
|
||||||
|
"status": "Status",
|
||||||
|
"remark": "Remarks",
|
||||||
|
"creator": "creator",
|
||||||
|
"modifier": "modifier",
|
||||||
|
"createTime": "Created At",
|
||||||
|
"operation": "Actions",
|
||||||
|
"updateTime": "Updated At"
|
||||||
|
}
|
||||||
114
web/apps/web-ele/src/locales/langs/zh-CN/system.json
Normal file
114
web/apps/web-ele/src/locales/langs/zh-CN/system.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"title": "系统管理",
|
||||||
|
"dept": {
|
||||||
|
"list": "部门列表",
|
||||||
|
"createTime": "创建时间",
|
||||||
|
"deptName": "部门名称",
|
||||||
|
"name": "部门",
|
||||||
|
"operation": "操作",
|
||||||
|
"parentDept": "上级部门",
|
||||||
|
"remark": "备注",
|
||||||
|
"status": "状态",
|
||||||
|
"title": "部门管理"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"list": "菜单列表",
|
||||||
|
"activeIcon": "激活图标",
|
||||||
|
"sort": "排序",
|
||||||
|
"activePath": "激活路径",
|
||||||
|
"activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径",
|
||||||
|
"activePathMustExist": "该路径未能找到有效的菜单",
|
||||||
|
"advancedSettings": "其它设置",
|
||||||
|
"affixTab": "固定在标签",
|
||||||
|
"auth_code": "权限标识",
|
||||||
|
"badge": "徽章内容",
|
||||||
|
"badgeVariants": "徽标样式",
|
||||||
|
"badgeType": {
|
||||||
|
"dot": "点",
|
||||||
|
"none": "无",
|
||||||
|
"normal": "文字",
|
||||||
|
"title": "徽标类型"
|
||||||
|
},
|
||||||
|
"component": "页面组件",
|
||||||
|
"hideChildrenInMenu": "隐藏子菜单",
|
||||||
|
"hideInBreadcrumb": "在面包屑中隐藏",
|
||||||
|
"hideInMenu": "隐藏菜单",
|
||||||
|
"hideInTab": "在标签栏中隐藏",
|
||||||
|
"icon": "图标",
|
||||||
|
"keepAlive": "缓存标签页",
|
||||||
|
"linkSrc": "链接地址",
|
||||||
|
"menuName": "菜单名称",
|
||||||
|
"menuTitle": "标题",
|
||||||
|
"name": "菜单",
|
||||||
|
"operation": "操作",
|
||||||
|
"parent": "上级菜单",
|
||||||
|
"path": "路由地址",
|
||||||
|
"status": "状态",
|
||||||
|
"title": "菜单管理",
|
||||||
|
"type": "类型",
|
||||||
|
"typeButton": "按钮",
|
||||||
|
"typeCatalog": "目录",
|
||||||
|
"typeEmbedded": "内嵌",
|
||||||
|
"typeLink": "外链",
|
||||||
|
"typeMenu": "菜单"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"title": "角色管理",
|
||||||
|
"list": "角色列表",
|
||||||
|
"name": "角色",
|
||||||
|
"roleName": "角色名称",
|
||||||
|
"id": "角色ID",
|
||||||
|
"status": "状态",
|
||||||
|
"remark": "备注",
|
||||||
|
"createTime": "创建时间",
|
||||||
|
"operation": "操作",
|
||||||
|
"permissions": "权限",
|
||||||
|
"setPermissions": "授权"
|
||||||
|
},
|
||||||
|
"tenant": {
|
||||||
|
"name": "租户",
|
||||||
|
"contact_mobile": "联系手机",
|
||||||
|
"website": "绑定域名",
|
||||||
|
"package_id": "租户套餐编号",
|
||||||
|
"expire_time": "过期时间",
|
||||||
|
"account_count": "账号数量",
|
||||||
|
"tenantName": "租户名称"
|
||||||
|
},
|
||||||
|
"tenant_package": {
|
||||||
|
"name": "租户套餐",
|
||||||
|
"website": "绑定域名",
|
||||||
|
"package_id": "租户套餐编号",
|
||||||
|
"expire_time": "过期时间",
|
||||||
|
"account_count": "账号数量",
|
||||||
|
"tenantName": "租户名称"
|
||||||
|
},
|
||||||
|
"dict_type": {
|
||||||
|
"name": "字典名称",
|
||||||
|
"title": "字典名称",
|
||||||
|
"type": "字典类型"
|
||||||
|
},
|
||||||
|
"dict_data": {
|
||||||
|
"name": "字典数据",
|
||||||
|
"title": "字典数据",
|
||||||
|
"type": "字典键值"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"name": "岗位",
|
||||||
|
"title": "岗位管理"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "用户",
|
||||||
|
"title": "用户管理"
|
||||||
|
},
|
||||||
|
"login_log": {
|
||||||
|
"name": "登录日志",
|
||||||
|
"title": "登录日志"
|
||||||
|
},
|
||||||
|
"status": "状态",
|
||||||
|
"remark": "备注",
|
||||||
|
"creator": "创建人",
|
||||||
|
"modifier": "修改人",
|
||||||
|
"createTime": "创建时间",
|
||||||
|
"operation": "操作",
|
||||||
|
"updateTime": "更新时间"
|
||||||
|
}
|
||||||
136
web/apps/web-ele/src/models/base.ts
Normal file
136
web/apps/web-ele/src/models/base.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export interface CoreModel {
|
||||||
|
remark: string;
|
||||||
|
creator: string;
|
||||||
|
modifier: string;
|
||||||
|
update_time: string;
|
||||||
|
create_time: string;
|
||||||
|
is_deleted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用Model基类
|
||||||
|
export class BaseModel<
|
||||||
|
T,
|
||||||
|
CreateData = Omit<T, any>,
|
||||||
|
UpdateData = Partial<CreateData>,
|
||||||
|
> {
|
||||||
|
protected baseUrl: string;
|
||||||
|
|
||||||
|
constructor(baseUrl: string) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用操作方法
|
||||||
|
* @param url 操作路径
|
||||||
|
* @param data 请求数据
|
||||||
|
* @param id 是否针对单条记录的操作
|
||||||
|
* @param method 请求方法
|
||||||
|
*/
|
||||||
|
async action(
|
||||||
|
url: string,
|
||||||
|
data: any = {},
|
||||||
|
id: null | number = null,
|
||||||
|
method = 'post',
|
||||||
|
) {
|
||||||
|
const baseUrl = id
|
||||||
|
? `${this.baseUrl}${id}/${url}/`
|
||||||
|
: `${this.baseUrl}${url}/`;
|
||||||
|
|
||||||
|
const config =
|
||||||
|
method === 'get'
|
||||||
|
? {
|
||||||
|
url: baseUrl,
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
url: baseUrl,
|
||||||
|
method,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
return requestClient.request(url, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建记录
|
||||||
|
*/
|
||||||
|
async create(data: CreateData) {
|
||||||
|
return requestClient.post(this.baseUrl, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除记录
|
||||||
|
*/
|
||||||
|
async delete(id: number) {
|
||||||
|
return requestClient.delete(`${this.baseUrl}${id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出数据
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 导出数据
|
||||||
|
*/
|
||||||
|
async export(params: Recordable<T> = {}) {
|
||||||
|
return requestClient.get(`${this.baseUrl}export/`, {
|
||||||
|
params,
|
||||||
|
responseType: 'blob', // 二进制流
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列表数据
|
||||||
|
*/
|
||||||
|
async list(params: Recordable<T> = {}) {
|
||||||
|
return requestClient.get<Array<T>>(this.baseUrl, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分更新记录
|
||||||
|
*/
|
||||||
|
async patch(id: number, data: Partial<UpdateData>) {
|
||||||
|
return requestClient.patch(`${this.baseUrl}${id}/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单条记录
|
||||||
|
*/
|
||||||
|
async retrieve(id: number) {
|
||||||
|
return requestClient.get<T>(`${this.baseUrl}${id}/`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 全量更新记录
|
||||||
|
*/
|
||||||
|
async update(id: number, data: UpdateData) {
|
||||||
|
return requestClient.put(`${this.baseUrl}${id}/`, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // 字典类型专用Model
|
||||||
|
// export class SystemDictTypeModel extends BaseModel<SystemDictTypeApi.SystemDictType> {
|
||||||
|
// constructor() {
|
||||||
|
// super('/system/dict_type/');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // AmazonListingModel示例
|
||||||
|
// export class AmazonListingModel extends BaseModel<AmazonListing> {
|
||||||
|
// constructor() {
|
||||||
|
// super('amazon/amazon_listing/');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// const dictTypeModel = new SystemDictTypeModel();
|
||||||
|
//
|
||||||
|
// // 获取列表
|
||||||
|
// dictTypeModel.list().then(({ data }) => console.log(data));
|
||||||
|
//
|
||||||
|
// // 创建记录
|
||||||
|
// dictTypeModel.create({ name: '新字典', type: 'new_type' });
|
||||||
|
//
|
||||||
|
// // 更新记录
|
||||||
|
// dictTypeModel.patch('123', { name: '更新后的字典' });
|
||||||
1
web/apps/web-ele/src/models/index.ts
Normal file
1
web/apps/web-ele/src/models/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './base';
|
||||||
23
web/apps/web-ele/src/models/system/login_log.ts
Normal file
23
web/apps/web-ele/src/models/system/login_log.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { BaseModel } from '#/models/base';
|
||||||
|
|
||||||
|
export namespace SystemLoginLogApi {
|
||||||
|
export interface SystemLoginLog {
|
||||||
|
id: number;
|
||||||
|
remark: string;
|
||||||
|
creator: string;
|
||||||
|
modifier: string;
|
||||||
|
update_time: string;
|
||||||
|
create_time: string;
|
||||||
|
is_deleted: boolean;
|
||||||
|
username: string;
|
||||||
|
result: number;
|
||||||
|
user_ip: string;
|
||||||
|
user_agent: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SystemLoginLogModel extends BaseModel<SystemLoginLogApi.SystemLoginLog> {
|
||||||
|
constructor() {
|
||||||
|
super('/system/login_log/');
|
||||||
|
}
|
||||||
|
}
|
||||||
23
web/apps/web-ele/src/models/system/post.ts
Normal file
23
web/apps/web-ele/src/models/system/post.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { BaseModel } from '#/models/base';
|
||||||
|
|
||||||
|
export namespace SystemPostApi {
|
||||||
|
export interface SystemPost {
|
||||||
|
id: number;
|
||||||
|
remark: string;
|
||||||
|
creator: string;
|
||||||
|
modifier: string;
|
||||||
|
update_time: string;
|
||||||
|
create_time: string;
|
||||||
|
is_deleted: boolean;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
sort: number;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SystemPostModel extends BaseModel<SystemPostApi.SystemPost> {
|
||||||
|
constructor() {
|
||||||
|
super('/system/post/');
|
||||||
|
}
|
||||||
|
}
|
||||||
40
web/apps/web-ele/src/models/system/user.ts
Normal file
40
web/apps/web-ele/src/models/system/user.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { BaseModel } from '#/models/base';
|
||||||
|
|
||||||
|
export namespace SystemUserApi {
|
||||||
|
export interface SystemUser {
|
||||||
|
id: number;
|
||||||
|
password: string;
|
||||||
|
last_login: string;
|
||||||
|
is_superuser: boolean;
|
||||||
|
username: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
is_staff: boolean;
|
||||||
|
is_active: boolean;
|
||||||
|
date_joined: string;
|
||||||
|
remark: string;
|
||||||
|
creator: string;
|
||||||
|
modifier: string;
|
||||||
|
update_time: string;
|
||||||
|
create_time: string;
|
||||||
|
is_deleted: boolean;
|
||||||
|
mobile: string;
|
||||||
|
nickname: string;
|
||||||
|
gender: number;
|
||||||
|
language: string;
|
||||||
|
city: string;
|
||||||
|
province: string;
|
||||||
|
country: string;
|
||||||
|
avatar_url: string;
|
||||||
|
status: number;
|
||||||
|
login_date: string;
|
||||||
|
login_ip: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SystemUserModel extends BaseModel<SystemUserApi.SystemUser> {
|
||||||
|
constructor() {
|
||||||
|
super('/system/user/');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,5 +9,6 @@ export const overridesPreferences = defineOverridesPreferences({
|
|||||||
// overrides
|
// overrides
|
||||||
app: {
|
app: {
|
||||||
name: import.meta.env.VITE_APP_TITLE,
|
name: import.meta.env.VITE_APP_TITLE,
|
||||||
|
accessMode: 'backend', // 或 'frontend'
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
102
web/apps/web-ele/src/router/routes/modules/system.ts
Normal file
102
web/apps/web-ele/src/router/routes/modules/system.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'ion:settings-outline',
|
||||||
|
order: 9997,
|
||||||
|
title: $t('system.title'),
|
||||||
|
},
|
||||||
|
name: 'System',
|
||||||
|
path: '/system',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/system/user',
|
||||||
|
name: 'SystemUser',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account-group',
|
||||||
|
title: $t('system.user.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/user/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/role',
|
||||||
|
name: 'SystemRole',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:account-group',
|
||||||
|
title: $t('system.role.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/role/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/menu',
|
||||||
|
name: 'SystemMenu',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:menu',
|
||||||
|
title: $t('system.menu.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/menu/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/dept',
|
||||||
|
name: 'SystemDept',
|
||||||
|
meta: {
|
||||||
|
icon: 'charm:organisation',
|
||||||
|
title: $t('system.dept.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/dept/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/post',
|
||||||
|
name: 'SystemPost',
|
||||||
|
meta: {
|
||||||
|
icon: 'charm:organisation',
|
||||||
|
title: $t('system.post.title'),
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/post/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/dict_type',
|
||||||
|
name: 'SystemDictType',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:menu',
|
||||||
|
title: '字典列表',
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/dict_type/list.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/system/dict_data',
|
||||||
|
name: 'SystemDictData',
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:menu',
|
||||||
|
title: '字典数据',
|
||||||
|
hideInMenu: true, // 关键配置:设置为 true 时菜单将被隐藏
|
||||||
|
// affix: true,
|
||||||
|
},
|
||||||
|
component: () => import('#/views/system/dict_data/list.vue'),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: '/system/tenants',
|
||||||
|
// name: 'SystemTenants',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'mdi:menu',
|
||||||
|
// title: '租户列表',
|
||||||
|
// },
|
||||||
|
// component: () => import('#/views/system/tenants/list.vue'),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/system/tenant_package',
|
||||||
|
// name: 'SystemTenantPackage',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'charm:organisation',
|
||||||
|
// title: '租户套餐',
|
||||||
|
// },
|
||||||
|
// component: () => import('#/views/system/tenant_package/list.vue'),
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -13,9 +13,13 @@ import { defineStore } from 'pinia';
|
|||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { usePermissionStore } from './permission';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const loginLoading = ref(false);
|
const loginLoading = ref(false);
|
||||||
@@ -63,7 +67,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.realName) {
|
||||||
ElNotification({
|
ElNotification({
|
||||||
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.username}`,
|
||||||
title: $t('authentication.loginSuccess'),
|
title: $t('authentication.loginSuccess'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
@@ -102,6 +106,11 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
userInfo = await getUserInfoApi();
|
userInfo = await getUserInfoApi();
|
||||||
userStore.setUserInfo(userInfo);
|
userStore.setUserInfo(userInfo);
|
||||||
|
// 设置权限
|
||||||
|
if (userInfo && Array.isArray(userInfo.permissions)) {
|
||||||
|
permissionStore.setPermissions(userInfo.permissions);
|
||||||
|
}
|
||||||
|
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
web/apps/web-ele/src/store/permission.ts
Normal file
30
web/apps/web-ele/src/store/permission.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const usePermissionStore = defineStore('permission', () => {
|
||||||
|
// 权限码列表
|
||||||
|
const permissions = ref<string[]>([]);
|
||||||
|
|
||||||
|
// 设置权限码
|
||||||
|
function setPermissions(perms: string[]) {
|
||||||
|
permissions.value = perms || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否有某个权限
|
||||||
|
function hasPermission(code: string): boolean {
|
||||||
|
return permissions.value.includes(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
function $reset() {
|
||||||
|
permissions.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
permissions,
|
||||||
|
setPermissions,
|
||||||
|
hasPermission,
|
||||||
|
$reset,
|
||||||
|
};
|
||||||
|
})
|
||||||
14
web/apps/web-ele/src/utils/date.ts
Normal file
14
web/apps/web-ele/src/utils/date.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化 ISO 时间字符串为 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
* @param value ISO 时间字符串
|
||||||
|
* @param format 自定义格式,默认 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
*/
|
||||||
|
export function format_datetime(
|
||||||
|
value: Date | string,
|
||||||
|
format = 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
): string {
|
||||||
|
if (!value) return '';
|
||||||
|
return dayjs(value).format(format);
|
||||||
|
}
|
||||||
5
web/apps/web-ele/src/utils/dict.ts
Normal file
5
web/apps/web-ele/src/utils/dict.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum DICT_TYPE {
|
||||||
|
USER_TYPE = 'user_type',
|
||||||
|
|
||||||
|
// TEMU_ORDER_STATUS = 'temu_order_status',
|
||||||
|
}
|
||||||
48
web/apps/web-ele/src/utils/permission.ts
Normal file
48
web/apps/web-ele/src/utils/permission.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import type { App, DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
|
import { usePermissionStore } from '#/store/permission';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限按钮option生成工具
|
||||||
|
* @param code 权限码
|
||||||
|
* @param option 按钮option或字符串
|
||||||
|
* @returns 有权限返回option,无权限返回false
|
||||||
|
*/
|
||||||
|
export function op(code: string, option: any) {
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
return permissionStore.hasPermission(code) ? option : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局权限判断函数,适用于模板v-if
|
||||||
|
*/
|
||||||
|
export function hasPermission(code: string): boolean {
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
return permissionStore.hasPermission(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v-permission 自定义指令
|
||||||
|
* 用法:v-permission="'system:user:create'"
|
||||||
|
* 或 v-permission="['system:user:create', 'system:user:update']"
|
||||||
|
*/
|
||||||
|
const permissionDirective = {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
|
||||||
|
const codes = Array.isArray(binding.value)
|
||||||
|
? binding.value
|
||||||
|
: [binding.value];
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const has = codes.some((code) => permissionStore.hasPermission(code));
|
||||||
|
if (!has) {
|
||||||
|
el.parentNode && el.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册全局权限指令
|
||||||
|
* @param app Vue App 实例
|
||||||
|
*/
|
||||||
|
export function registerPermissionDirective(app: App) {
|
||||||
|
app.directive('permission', permissionDirective);
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
import type { BasicOption } from '@vben/types';
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
import { computed, markRaw } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
@@ -24,7 +24,7 @@ const MOCK_USER_OPTIONS: BasicOption[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'User',
|
label: 'User',
|
||||||
value: 'jack',
|
value: 'chenze',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
);
|
);
|
||||||
if (findUser) {
|
if (findUser) {
|
||||||
form.setValues({
|
form.setValues({
|
||||||
password: '123456',
|
password: 'admin123',
|
||||||
username: findUser.value,
|
username: findUser.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -78,13 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
label: $t('authentication.password'),
|
label: $t('authentication.password'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
component: markRaw(SliderCaptcha),
|
// component: markRaw(SliderCaptcha),
|
||||||
fieldName: 'captcha',
|
// fieldName: 'captcha',
|
||||||
rules: z.boolean().refine((value) => value, {
|
// rules: z.boolean().refine((value) => value, {
|
||||||
message: $t('authentication.verifyRequiredTip'),
|
// message: $t('authentication.verifyRequiredTip'),
|
||||||
}),
|
// }),
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
160
web/apps/web-ele/src/views/system/dept/data.ts
Normal file
160
web/apps/web-ele/src/views/system/dept/data.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getDeptList } from '#/api/system/dept';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
import { op } from '#/utils/permission';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: getDeptList,
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
},
|
||||||
|
fieldName: 'pid',
|
||||||
|
label: $t('system.dept.parentDept'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: $t('system.dept.deptName'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2]))
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'sort',
|
||||||
|
label: '显示排序',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'leader',
|
||||||
|
label: '负责人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'phone',
|
||||||
|
label: '联系电话',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.dept.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
maxLength: 50,
|
||||||
|
rows: 3,
|
||||||
|
showCount: true,
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: $t('system.dept.remark'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.max(50, $t('ui.formRules.maxLength', [$t('system.dept.remark'), 50]))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemDeptApi.SystemDept>,
|
||||||
|
): VxeTableGridOptions<SystemDeptApi.SystemDept>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
title: $t('system.dept.deptName'),
|
||||||
|
treeNode: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sort',
|
||||||
|
title: '排序',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'status',
|
||||||
|
title: $t('system.dept.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: $t('system.dept.createTime'),
|
||||||
|
width: 180,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: $t('system.dept.remark'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.dept.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
op('system:dept:create', { code: 'append', text: '新增下级' }),
|
||||||
|
op('system:dept:edit', 'edit'),
|
||||||
|
op('system:dept:delete', {
|
||||||
|
code: 'delete',
|
||||||
|
disabled: (row: SystemDeptApi.SystemDept) => {
|
||||||
|
return !!(row.children && row.children.length > 0);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
].filter(Boolean),
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: $t('system.dept.operation'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
147
web/apps/web-ele/src/views/system/dept/list.vue
Normal file
147
web/apps/web-ele/src/views/system/dept/list.vue
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteDept, getDeptList } from '#/api/system/dept';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑部门
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemDeptApi.SystemDept) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加下级部门
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onAppend(row: SystemDeptApi.SystemDept) {
|
||||||
|
formModalApi.setData({ pid: row.id }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新部门
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除部门
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemDeptApi.SystemDept) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteDept(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemDeptApi.SystemDept>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'append': {
|
||||||
|
onAppend(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (_params) => {
|
||||||
|
return await getDeptList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="部门列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
@click="onCreate"
|
||||||
|
v-permission="'system:dept:create'"
|
||||||
|
>
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.dept.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
80
web/apps/web-ele/src/views/system/dept/modules/form.vue
Normal file
80
web/apps/web-ele/src/views/system/dept/modules/form.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createDept, updateDept } from '#/api/system/dept';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemDeptApi.SystemDept>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.dept.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.dept.name')]);
|
||||||
|
});
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||||
|
const isHorizontal = computed(() => breakpoints.greaterOrEqual('md').value);
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateDept(formData.value.id, data)
|
||||||
|
: createDept(data));
|
||||||
|
modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemDeptApi.SystemDept>();
|
||||||
|
if (data) {
|
||||||
|
if (data.pid === 0) {
|
||||||
|
data.pid = undefined;
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form class="mx-4" :layout="isHorizontal ? 'horizontal' : 'vertical'" />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
211
web/apps/web-ele/src/views/system/dict_data/data.ts
Normal file
211
web/apps/web-ele/src/views/system/dict_data/data.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDictDataApi } from '#/api/system/dict_data';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getDictTypeList } from '#/api/system/dict_type';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import {format_datetime} from "#/utils/date";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: getDictTypeList,
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
fieldName: 'dict_type',
|
||||||
|
label: '字典类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'label',
|
||||||
|
label: '字典标签',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2]))
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dict_data.type'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'value',
|
||||||
|
label: '字典键值',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dict_data.type'), 2]))
|
||||||
|
.max(
|
||||||
|
50,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dict_data.type'), 50]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'sort',
|
||||||
|
label: '字典排序',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'color_type',
|
||||||
|
label: '颜色类型',
|
||||||
|
componentProps: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'default',
|
||||||
|
label: '默认',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'primary',
|
||||||
|
label: '主要',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'success',
|
||||||
|
label: '成功',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'info',
|
||||||
|
label: '信息',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'warning',
|
||||||
|
label: '警告',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'danger',
|
||||||
|
label: '危险',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'css_class',
|
||||||
|
label: 'CSS Class',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: '开启', value: 1 },
|
||||||
|
{ label: '关闭', value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
maxLength: 50,
|
||||||
|
rows: 3,
|
||||||
|
showCount: true,
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50]))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemDictDataApi.SystemDictData>,
|
||||||
|
): VxeTableGridOptions<SystemDictDataApi.SystemDictData>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'id',
|
||||||
|
fixed: 'left',
|
||||||
|
title: '字典编码',
|
||||||
|
treeNode: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'label',
|
||||||
|
title: '字典标签',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'value',
|
||||||
|
title: '字典键值',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sort',
|
||||||
|
title: '字典排序',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'color_type',
|
||||||
|
title: '颜色类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'css_class',
|
||||||
|
title: 'CSS Class',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.dict_data.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
{
|
||||||
|
code: 'delete', // 默认的删除按钮
|
||||||
|
disabled: (row: SystemDictDataApi.SystemDictData) => {
|
||||||
|
return !!(row.children && row.children.length > 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
143
web/apps/web-ele/src/views/system/dict_data/list.vue
Normal file
143
web/apps/web-ele/src/views/system/dict_data/list.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDictDataApi } from '#/api/system/dict_data';
|
||||||
|
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteDictData, getDictDataList } from '#/api/system/dict_data';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑套餐
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemDictDataApi.SystemDictData) {
|
||||||
|
if (row.menu_ids) {
|
||||||
|
row.menu_ids = row.menu_ids.split(',').map(Number);
|
||||||
|
}
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新套餐
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除套餐
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemDictDataApi.SystemDictData) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteDictData(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemDictDataApi.SystemDictData>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
const { dict_type } = route.query;
|
||||||
|
return await getDictDataList({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
dict_type,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="字典数据">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.dict_data.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
82
web/apps/web-ele/src/views/system/dict_data/modules/form.vue
Normal file
82
web/apps/web-ele/src/views/system/dict_data/modules/form.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemDictDataApi } from '#/api/system/dict_data';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createDictData, updateDictData } from '#/api/system/dict_data';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemDictDataApi.SystemDictData>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.dict_data.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.dict_data.name')]);
|
||||||
|
});
|
||||||
|
const route = useRoute();
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
const { dict_type } = route.query;
|
||||||
|
data.dict_type = dict_type;
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateDictData(formData.value.id, data)
|
||||||
|
: createDictData(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemDictDataApi.SystemDictData>();
|
||||||
|
if (data) {
|
||||||
|
if (data.pid === 0) {
|
||||||
|
data.pid = undefined;
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped></style>
|
||||||
147
web/apps/web-ele/src/views/system/dict_type/data.ts
Normal file
147
web/apps/web-ele/src/views/system/dict_type/data.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDictTypeApi } from '#/api/system/dict_type';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '字典名称',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dict_type.name'), 2]))
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dict_type.name'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'type',
|
||||||
|
label: '字典类型',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dict_type.type'), 2]))
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dict_type.type'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: '开启', value: 1 },
|
||||||
|
{ label: '关闭', value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
maxLength: 50,
|
||||||
|
rows: 3,
|
||||||
|
showCount: true,
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50]))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemDictTypeApi.SystemDictType>,
|
||||||
|
): VxeTableGridOptions<SystemDictTypeApi.SystemDictType>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'id',
|
||||||
|
fixed: 'left',
|
||||||
|
title: '字典编号',
|
||||||
|
treeNode: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '字典名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
title: '字典类型',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.dict_type.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
{
|
||||||
|
code: 'view', // 新增查看详情按钮(可自定义code)
|
||||||
|
text: '数据', // 按钮文本(国际化)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'delete', // 默认的删除按钮
|
||||||
|
disabled: (row: SystemDictTypeApi.SystemDictType) => {
|
||||||
|
return !!(row.children && row.children.length > 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
150
web/apps/web-ele/src/views/system/dict_type/list.vue
Normal file
150
web/apps/web-ele/src/views/system/dict_type/list.vue
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemDictTypeApi } from '#/api/system/dict_type';
|
||||||
|
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteDictType, getDictTypeList } from '#/api/system/dict_type';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑字典
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemDictTypeApi.SystemDictType) {
|
||||||
|
if (row.menu_ids) {
|
||||||
|
row.menu_ids = row.menu_ids.split(',').map(Number);
|
||||||
|
}
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新字典
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemDictTypeApi.SystemDictType) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteDictType(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const handleViewDetail = (row: SystemDictTypeApi.SystemDictType) => {
|
||||||
|
router.push({
|
||||||
|
path: '/system/dict_data/', // 目标页面路径
|
||||||
|
query: { dict_type: row.id }, // 传递查询参数
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemDictTypeApi.SystemDictType>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'view': {
|
||||||
|
handleViewDetail(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getDictTypeList({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="字典列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.dict_type.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
79
web/apps/web-ele/src/views/system/dict_type/modules/form.vue
Normal file
79
web/apps/web-ele/src/views/system/dict_type/modules/form.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemDictTypeApi } from '#/api/system/dict_type';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createDictType, updateDictType } from '#/api/system/dict_type';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemDictTypeApi.SystemDictType>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.dict_type.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.dict_type.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateDictType(formData.value.id, data)
|
||||||
|
: createDictType(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemDictTypeApi.SystemDictType>();
|
||||||
|
if (data) {
|
||||||
|
if (data.pid === 0) {
|
||||||
|
data.pid = undefined;
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped></style>
|
||||||
95
web/apps/web-ele/src/views/system/login_log/data.ts
Normal file
95
web/apps/web-ele/src/views/system/login_log/data.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { SystemLoginLogApi } from '#/models/system/login_log';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'username',
|
||||||
|
label: 'username',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['username']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['username', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'result',
|
||||||
|
label: 'result',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'user_ip',
|
||||||
|
label: 'user ip',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['user ip']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['user ip', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'user_agent',
|
||||||
|
label: 'user agent',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['user agent']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['user agent', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: 'remark',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['remark']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['remark', 100])),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
*/
|
||||||
|
export function useColumns(): VxeTableGridOptions<SystemLoginLogApi.SystemLoginLog>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
title: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
},
|
||||||
|
field: 'result_text',
|
||||||
|
title: '登录结果',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'user_ip',
|
||||||
|
title: '登录地址',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'user_agent',
|
||||||
|
title: '浏览器',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: $t('system.createTime'),
|
||||||
|
width: 150,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
47
web/apps/web-ele/src/views/system/login_log/list.vue
Normal file
47
web/apps/web-ele/src/views/system/login_log/list.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { SystemLoginLogModel } from '#/models/system/login_log';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
|
||||||
|
const formModel = new SystemLoginLogModel();
|
||||||
|
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await formModel.list({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="系统访问记录" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
79
web/apps/web-ele/src/views/system/login_log/modules/form.vue
Normal file
79
web/apps/web-ele/src/views/system/login_log/modules/form.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemLoginLogApi } from '#/models/system/login_log';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemLoginLogModel } from '#/models/system/login_log';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formModel = new SystemLoginLogModel();
|
||||||
|
|
||||||
|
const formData = ref<SystemLoginLogApi.SystemLoginLog>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.login_log.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.login_log.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? formModel.update(formData.value.id, data)
|
||||||
|
: formModel.create(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemLoginLogApi.SystemLoginLog>();
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped></style>
|
||||||
114
web/apps/web-ele/src/views/system/menu/data.ts
Normal file
114
web/apps/web-ele/src/views/system/menu/data.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemMenuApi } from '#/api/system/menu';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
export function getMenuTypeOptions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
color: 'processing',
|
||||||
|
label: $t('system.menu.typeCatalog'),
|
||||||
|
value: 'catalog',
|
||||||
|
},
|
||||||
|
{ color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' },
|
||||||
|
{ color: 'error', label: $t('system.menu.typeButton'), value: 'button' },
|
||||||
|
{
|
||||||
|
color: 'success',
|
||||||
|
label: $t('system.menu.typeEmbedded'),
|
||||||
|
value: 'embedded',
|
||||||
|
},
|
||||||
|
{ color: 'warning', label: $t('system.menu.typeLink'), value: 'link' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick: OnActionClickFn<SystemMenuApi.SystemMenu>,
|
||||||
|
): VxeTableGridOptions<SystemMenuApi.SystemMenu>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'meta.title',
|
||||||
|
fixed: 'left',
|
||||||
|
slots: { default: 'title' },
|
||||||
|
title: $t('system.menu.menuTitle'),
|
||||||
|
treeNode: true,
|
||||||
|
width: 250,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
cellRender: { name: 'CellTag', options: getMenuTypeOptions() },
|
||||||
|
field: 'type',
|
||||||
|
title: $t('system.menu.type'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'auth_code',
|
||||||
|
title: $t('system.menu.auth_code'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sort',
|
||||||
|
title: $t('system.menu.sort'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'path',
|
||||||
|
title: $t('system.menu.path'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'component',
|
||||||
|
formatter: ({ row }) => {
|
||||||
|
switch (row.type) {
|
||||||
|
case 'catalog':
|
||||||
|
case 'menu': {
|
||||||
|
return row.component ?? '';
|
||||||
|
}
|
||||||
|
case 'embedded': {
|
||||||
|
return row.meta?.iframeSrc ?? '';
|
||||||
|
}
|
||||||
|
case 'link': {
|
||||||
|
return row.meta?.link ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
minWidth: 200,
|
||||||
|
title: $t('system.menu.component'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'status',
|
||||||
|
title: $t('system.menu.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
code: 'append',
|
||||||
|
text: '新增下级',
|
||||||
|
},
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
'delete', // 默认的删除按钮
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: $t('system.menu.operation'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
162
web/apps/web-ele/src/views/system/menu/list.vue
Normal file
162
web/apps/web-ele/src/views/system/menu/list.vue
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon, Plus } from '@vben/icons';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import { MenuBadge } from '@vben-core/menu-ui';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteMenu, getMenuList, SystemMenuApi } from '#/api/system/menu';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (_params) => {
|
||||||
|
return await getMenuList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemMenuApi.SystemMenu>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'append': {
|
||||||
|
onAppend(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
function onEdit(row: SystemMenuApi.SystemMenu) {
|
||||||
|
formDrawerApi.setData(row).open();
|
||||||
|
}
|
||||||
|
function onCreate() {
|
||||||
|
formDrawerApi.setData({}).open();
|
||||||
|
}
|
||||||
|
function onAppend(row: SystemMenuApi.SystemMenu) {
|
||||||
|
formDrawerApi.setData({ pid: row.id }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDelete(row: SystemMenuApi.SystemMenu) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteMenu(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormDrawer @success="onRefresh" />
|
||||||
|
<Grid>
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.menu.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<template #title="{ row }">
|
||||||
|
<div class="flex w-full items-center gap-1">
|
||||||
|
<div class="size-5 flex-shrink-0">
|
||||||
|
<IconifyIcon
|
||||||
|
v-if="row.type === 'button'"
|
||||||
|
icon="carbon:security"
|
||||||
|
class="size-full"
|
||||||
|
/>
|
||||||
|
<IconifyIcon
|
||||||
|
v-else-if="row.meta?.icon"
|
||||||
|
:icon="row.meta?.icon || 'carbon:circle-dash'"
|
||||||
|
class="size-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="flex-auto">{{ $t(row.meta?.title) }}</span>
|
||||||
|
<div class="items-center justify-end"></div>
|
||||||
|
</div>
|
||||||
|
<MenuBadge
|
||||||
|
v-if="row.meta?.badgeType"
|
||||||
|
class="menu-badge"
|
||||||
|
:badge="row.meta.badge"
|
||||||
|
:badge-type="row.meta.badgeType"
|
||||||
|
:badge-variants="row.meta.badgeVariants"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.menu-badge {
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
& > :deep(div) {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
524
web/apps/web-ele/src/views/system/menu/modules/form.vue
Normal file
524
web/apps/web-ele/src/views/system/menu/modules/form.vue
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ChangeEvent } from 'ant-design-vue/es/_util/EventInterface';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, h, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { $te } from '@vben/locales';
|
||||||
|
import { getPopupContainer } from '@vben/utils';
|
||||||
|
|
||||||
|
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { useVbenForm, z } from '#/adapter/form';
|
||||||
|
import {
|
||||||
|
createMenu,
|
||||||
|
getMenuList,
|
||||||
|
isMenuPathExists,
|
||||||
|
isMenuSearchExists,
|
||||||
|
SystemMenuApi,
|
||||||
|
updateMenu,
|
||||||
|
} from '#/api/system/menu';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { componentKeys } from '#/router/routes';
|
||||||
|
|
||||||
|
import { getMenuTypeOptions } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
success: [];
|
||||||
|
}>();
|
||||||
|
const formData = ref<SystemMenuApi.SystemMenu>();
|
||||||
|
const titleSuffix = ref<string>();
|
||||||
|
const schema: VbenFormSchema[] = [
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: getMenuTypeOptions(),
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 'menu',
|
||||||
|
fieldName: 'type',
|
||||||
|
formItemClass: 'col-span-2 md:col-span-2',
|
||||||
|
label: $t('system.menu.type'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: $t('system.menu.menuName'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.menu.menuName'), 2]))
|
||||||
|
.max(30, $t('ui.formRules.maxLength', [$t('system.menu.menuName'), 30]))
|
||||||
|
.refine(
|
||||||
|
async (value: string) => {
|
||||||
|
return !(await isMenuSearchExists(
|
||||||
|
value,
|
||||||
|
formData.value?.id,
|
||||||
|
formData.value?.pid,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
(value) => ({
|
||||||
|
message: $t('ui.formRules.alreadyExists', [
|
||||||
|
$t('system.menu.menuName'),
|
||||||
|
value,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: getMenuList,
|
||||||
|
class: 'w-full',
|
||||||
|
filterTreeNode(input: string, node: Recordable<any>) {
|
||||||
|
if (!input || input.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const title: string = node.meta?.title ?? '';
|
||||||
|
if (!title) return false;
|
||||||
|
return title.includes(input) || $t(title).includes(input);
|
||||||
|
},
|
||||||
|
getPopupContainer,
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'meta.title',
|
||||||
|
showSearch: true,
|
||||||
|
treeDefaultExpandAll: true,
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
},
|
||||||
|
fieldName: 'pid',
|
||||||
|
label: $t('system.menu.parent'),
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
title({ label, meta }: { label: string; meta: Recordable<any> }) {
|
||||||
|
const coms = [];
|
||||||
|
if (!label) return '';
|
||||||
|
if (meta?.icon) {
|
||||||
|
coms.push(h(IconifyIcon, { class: 'size-4', icon: meta.icon }));
|
||||||
|
}
|
||||||
|
coms.push(h('span', { class: '' }, $t(label || '')));
|
||||||
|
return h('div', { class: 'flex items-center gap-1' }, coms);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps() {
|
||||||
|
// 不需要处理多语言时就无需这么做
|
||||||
|
return {
|
||||||
|
addonAfter: titleSuffix.value,
|
||||||
|
onChange({ target: { value } }: ChangeEvent) {
|
||||||
|
titleSuffix.value = value && $te(value) ? $t(value) : undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
fieldName: 'meta.title',
|
||||||
|
label: $t('system.menu.menuTitle'),
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['catalog', 'embedded', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'path',
|
||||||
|
label: $t('system.menu.path'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
|
||||||
|
.refine(
|
||||||
|
(value: string) => {
|
||||||
|
return value.startsWith('/');
|
||||||
|
},
|
||||||
|
$t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
async (value: string) => {
|
||||||
|
return !(await isMenuPathExists(value, formData.value?.id));
|
||||||
|
},
|
||||||
|
(value) => ({
|
||||||
|
message: $t('ui.formRules.alreadyExists', [
|
||||||
|
$t('system.menu.path'),
|
||||||
|
value,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['embedded', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'activePath',
|
||||||
|
help: $t('system.menu.activePathHelp'),
|
||||||
|
label: $t('system.menu.activePath'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
|
||||||
|
.refine(
|
||||||
|
(value: string) => {
|
||||||
|
return value.startsWith('/');
|
||||||
|
},
|
||||||
|
$t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
|
||||||
|
)
|
||||||
|
.refine(async (value: string) => {
|
||||||
|
return await isMenuPathExists(value, formData.value?.id);
|
||||||
|
}, $t('system.menu.activePathMustExist'))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'IconPicker',
|
||||||
|
componentProps: {
|
||||||
|
prefix: 'carbon',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['catalog', 'embedded', 'link', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.icon',
|
||||||
|
label: $t('system.menu.icon'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'IconPicker',
|
||||||
|
componentProps: {
|
||||||
|
prefix: 'carbon',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['catalog', 'embedded', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.activeIcon',
|
||||||
|
label: $t('system.menu.activeIcon'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'AutoComplete',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
class: 'w-full',
|
||||||
|
filterOption(input: string, option: { value: string }) {
|
||||||
|
return option.value.toLowerCase().includes(input.toLowerCase());
|
||||||
|
},
|
||||||
|
options: componentKeys.map((v) => ({ value: v })),
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules: (values) => {
|
||||||
|
return values.type === 'menu' ? 'required' : null;
|
||||||
|
},
|
||||||
|
show: (values) => {
|
||||||
|
return values.type === 'menu';
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'component',
|
||||||
|
label: $t('system.menu.component'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['embedded', 'link'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'linkSrc',
|
||||||
|
label: $t('system.menu.linkSrc'),
|
||||||
|
rules: z.string().url($t('ui.formRules.invalidURL')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
rules: (values) => {
|
||||||
|
return values.type === 'action' ? 'required' : null;
|
||||||
|
},
|
||||||
|
show: (values) => {
|
||||||
|
return ['action', 'button', 'catalog', 'embedded', 'menu'].includes(
|
||||||
|
values.type,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'auth_code',
|
||||||
|
label: $t('system.menu.auth_code'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
dependencies: {
|
||||||
|
rules: () => {
|
||||||
|
return 'required';
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'sort',
|
||||||
|
label: $t('system.menu.sort'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.menu.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
class: 'w-full',
|
||||||
|
options: [
|
||||||
|
{ label: $t('system.menu.badgeType.dot'), value: 'dot' },
|
||||||
|
{ label: $t('system.menu.badgeType.normal'), value: 'normal' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
// return values.type !== 'action';
|
||||||
|
return !['action', 'button'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.badgeType',
|
||||||
|
label: $t('system.menu.badgeType.title'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: (values) => {
|
||||||
|
return {
|
||||||
|
allowClear: true,
|
||||||
|
class: 'w-full',
|
||||||
|
disabled: values.meta?.badgeType !== 'normal',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.badge',
|
||||||
|
label: $t('system.menu.badge'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
class: 'w-full',
|
||||||
|
options: SystemMenuApi.BadgeVariants.map((v) => ({
|
||||||
|
label: v,
|
||||||
|
value: v,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.badgeVariants',
|
||||||
|
label: $t('system.menu.badgeVariants'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Divider',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button', 'link'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'divider1',
|
||||||
|
formItemClass: 'col-span-2 md:col-span-2 pb-0',
|
||||||
|
hideLabel: true,
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.advancedSettings'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.keepAlive',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.keepAlive'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['embedded', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.affixTab',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.affixTab'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.hide_in_menu',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.hideInMenu'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return ['catalog', 'menu'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.hide_children_in_menu',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.hideChildrenInMenu'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button', 'link'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.hideInBreadcrumb',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.hideInBreadcrumb'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Checkbox',
|
||||||
|
dependencies: {
|
||||||
|
show: (values) => {
|
||||||
|
return !['action', 'button', 'link'].includes(values.type);
|
||||||
|
},
|
||||||
|
triggerFields: ['type'],
|
||||||
|
},
|
||||||
|
fieldName: 'meta.hideInTab',
|
||||||
|
renderComponentContent() {
|
||||||
|
return {
|
||||||
|
default: () => $t('system.menu.hideInTab'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||||
|
const isHorizontal = computed(() => breakpoints.greaterOrEqual('md').value);
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
colon: true,
|
||||||
|
formItemClass: 'col-span-2 md:col-span-1',
|
||||||
|
},
|
||||||
|
schema,
|
||||||
|
showDefaultActions: false,
|
||||||
|
wrapperClass: 'grid-cols-2 gap-x-4',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onConfirm: onSubmit,
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = drawerApi.getData<SystemMenuApi.SystemMenu>();
|
||||||
|
if (data?.type === 'link') {
|
||||||
|
data.linkSrc = data.meta?.link;
|
||||||
|
} else if (data?.type === 'embedded') {
|
||||||
|
data.linkSrc = data.meta?.iframeSrc;
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
titleSuffix.value = formData.value.meta?.title
|
||||||
|
? $t(formData.value.meta.title)
|
||||||
|
: '';
|
||||||
|
} else {
|
||||||
|
formApi.resetForm();
|
||||||
|
titleSuffix.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
drawerApi.lock();
|
||||||
|
const data =
|
||||||
|
await formApi.getValues<
|
||||||
|
Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>
|
||||||
|
>();
|
||||||
|
if (data.type === 'link') {
|
||||||
|
data.meta = { ...data.meta, link: data.linkSrc };
|
||||||
|
} else if (data.type === 'embedded') {
|
||||||
|
data.meta = { ...data.meta, iframeSrc: data.linkSrc };
|
||||||
|
}
|
||||||
|
delete data.linkSrc;
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateMenu(formData.value.id, data)
|
||||||
|
: createMenu(data));
|
||||||
|
drawerApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
drawerApi.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getDrawerTitle = computed(() =>
|
||||||
|
formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.menu.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.menu.name')]),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Drawer class="w-full max-w-[800px]" :title="getDrawerTitle">
|
||||||
|
<Form class="mx-4" :layout="isHorizontal ? 'horizontal' : 'vertical'" />
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
124
web/apps/web-ele/src/views/system/post/data.ts
Normal file
124
web/apps/web-ele/src/views/system/post/data.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemPostApi } from '#/models/system/post';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '名称',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['名称']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['名称', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'code',
|
||||||
|
label: '编码',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['编码']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['编码', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'sort',
|
||||||
|
label: '排序',
|
||||||
|
rules: z.number(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: '开启', value: true },
|
||||||
|
{ label: '关闭', value: false },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: true,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '是否启用',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemPostApi.SystemPost>,
|
||||||
|
): VxeTableGridOptions<SystemPostApi.SystemPost>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '岗位名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'code',
|
||||||
|
title: '岗位编码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'sort',
|
||||||
|
title: '排序',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.post.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
{
|
||||||
|
code: 'delete', // 默认的删除按钮
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
132
web/apps/web-ele/src/views/system/post/list.vue
Normal file
132
web/apps/web-ele/src/views/system/post/list.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemPostApi } from '#/models/system/post';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemPostModel } from '#/models/system/post';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const formModel = new SystemPostModel();
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑岗位信息表
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemPostApi.SystemPost) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新岗位信息表
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除岗位信息表
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemPostApi.SystemPost) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: '删除岗位信息表',
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
formModel
|
||||||
|
.delete(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: '删除成功',
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemPostApi.SystemPost>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await formModel.list({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="岗位信息表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.post.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
79
web/apps/web-ele/src/views/system/post/modules/form.vue
Normal file
79
web/apps/web-ele/src/views/system/post/modules/form.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemPostApi } from '#/models/system/post';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemPostModel } from '#/models/system/post';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formModel = new SystemPostModel();
|
||||||
|
|
||||||
|
const formData = ref<SystemPostApi.SystemPost>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.post.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.post.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? formModel.update(formData.value.id, data)
|
||||||
|
: formModel.create(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemPostApi.SystemPost>();
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped></style>
|
||||||
118
web/apps/web-ele/src/views/system/role/data.ts
Normal file
118
web/apps/web-ele/src/views/system/role/data.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemRoleApi } from '#/api/system/role';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: $t('system.role.roleName'),
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.role.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Textarea',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: $t('system.role.remark'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'permissions',
|
||||||
|
formItemClass: 'items-start',
|
||||||
|
label: $t('system.role.setPermissions'),
|
||||||
|
modelPropName: 'modelValue',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: $t('system.role.roleName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.role.status'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColumns<T = SystemRoleApi.SystemRole>(
|
||||||
|
onActionClick: OnActionClickFn<T>,
|
||||||
|
onStatusChange?: (newStatus: any, row: T) => PromiseLike<boolean | undefined>,
|
||||||
|
): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: $t('system.role.roleName'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: $t('system.role.id'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
attrs: { beforeChange: onStatusChange },
|
||||||
|
name: onStatusChange ? 'CellSwitch' : 'CellTag',
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: $t('system.role.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
minWidth: 100,
|
||||||
|
title: $t('system.role.remark'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: $t('system.role.createTime'),
|
||||||
|
width: 200,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.role.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
title: $t('system.role.operation'),
|
||||||
|
width: 130,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
166
web/apps/web-ele/src/views/system/role/list.vue
Normal file
166
web/apps/web-ele/src/views/system/role/list.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemRoleApi } from '#/api/system/role';
|
||||||
|
|
||||||
|
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message, Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteRole, getRoleList, patchRole } from '#/api/system/role';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns, useGridFormSchema } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
fieldMappingTime: [['createTime', ['startTime', 'endTime']]],
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick, onStatusChange),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getRoleList({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
search: true,
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<SystemRoleApi.SystemRole>,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onActionClick(e: OnActionClickParams<SystemRoleApi.SystemRole>) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Antd的Modal.confirm封装为promise,方便在异步函数中调用。
|
||||||
|
* @param content 提示内容
|
||||||
|
* @param title 提示标题
|
||||||
|
*/
|
||||||
|
function confirm(content: string, title: string) {
|
||||||
|
return new Promise((reslove, reject) => {
|
||||||
|
Modal.confirm({
|
||||||
|
content,
|
||||||
|
onCancel() {
|
||||||
|
reject(new Error('已取消'));
|
||||||
|
},
|
||||||
|
onOk() {
|
||||||
|
reslove(true);
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态开关即将改变
|
||||||
|
* @param newStatus 期望改变的状态值
|
||||||
|
* @param row 行数据
|
||||||
|
* @returns 返回false则中止改变,返回其他值(undefined、true)则允许改变
|
||||||
|
*/
|
||||||
|
async function onStatusChange(
|
||||||
|
newStatus: number,
|
||||||
|
row: SystemRoleApi.SystemRole,
|
||||||
|
) {
|
||||||
|
const status: Recordable<string> = {
|
||||||
|
0: '禁用',
|
||||||
|
1: '启用',
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await confirm(
|
||||||
|
`你要将${row.name}的状态切换为 【${status[newStatus.toString()]}】 吗?`,
|
||||||
|
`切换状态`,
|
||||||
|
);
|
||||||
|
await patchRole(row.id, { status: newStatus });
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit(row: SystemRoleApi.SystemRole) {
|
||||||
|
formDrawerApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDelete(row: SystemRoleApi.SystemRole) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteRole(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreate() {
|
||||||
|
formDrawerApi.setData({}).open();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormDrawer @success="onRefresh"/>
|
||||||
|
<Grid :table-title="$t('system.role.list')">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.role.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
138
web/apps/web-ele/src/views/system/role/modules/form.vue
Normal file
138
web/apps/web-ele/src/views/system/role/modules/form.vue
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DataNode } from 'ant-design-vue/es/tree';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { SystemRoleApi } from '#/api/system/role';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer, VbenTree } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Spin } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getMenuList } from '#/api/system/menu';
|
||||||
|
import { createRole, updateRole } from '#/api/system/role';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref<SystemRoleApi.SystemRole>();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissions = ref<DataNode[]>([]);
|
||||||
|
const loadingPermissions = ref(false);
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
drawerApi.lock();
|
||||||
|
(id.value ? updateRole(id.value, values) : createRole(values))
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
drawerApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
drawerApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = drawerApi.getData<SystemRoleApi.SystemRole>();
|
||||||
|
formApi.resetForm();
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
formApi.setValues(data);
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
if (permissions.value.length === 0) {
|
||||||
|
loadPermissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadPermissions() {
|
||||||
|
loadingPermissions.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getMenuList();
|
||||||
|
permissions.value = res as unknown as DataNode[];
|
||||||
|
} finally {
|
||||||
|
loadingPermissions.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('common.edit', $t('system.role.name'))
|
||||||
|
: $t('common.create', $t('system.role.name'));
|
||||||
|
});
|
||||||
|
|
||||||
|
function getNodeClass(node: Recordable<any>) {
|
||||||
|
const classes: string[] = [];
|
||||||
|
if (node.value?.type === 'button') {
|
||||||
|
classes.push('inline-flex');
|
||||||
|
if (node.index % 3 >= 1) {
|
||||||
|
classes.push('!pl-0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(' ');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Drawer :title="getDrawerTitle">
|
||||||
|
<Form>
|
||||||
|
<template #permissions="slotProps">
|
||||||
|
<Spin v-if="permissions.length" :spinning="loadingPermissions" wrapper-class-name="w-full">
|
||||||
|
<VbenTree
|
||||||
|
:tree-data="permissions"
|
||||||
|
multiple
|
||||||
|
bordered
|
||||||
|
:default-expanded-level="2"
|
||||||
|
:get-node-class="getNodeClass"
|
||||||
|
v-bind="slotProps"
|
||||||
|
value-field="id"
|
||||||
|
label-field="meta.title"
|
||||||
|
icon-field="meta.icon"
|
||||||
|
>
|
||||||
|
<template #node="{ value }">
|
||||||
|
<IconifyIcon v-if="value.meta.icon" :icon="value.meta.icon" />
|
||||||
|
{{ $t(value.meta.title) }}
|
||||||
|
</template>
|
||||||
|
</VbenTree>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
:deep(.ant-tree-title) {
|
||||||
|
.tree-actions {
|
||||||
|
display: none;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree-title:hover) {
|
||||||
|
.tree-actions {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
133
web/apps/web-ele/src/views/system/tenant_package/data.ts
Normal file
133
web/apps/web-ele/src/views/system/tenant_package/data.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant_package';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getTenantPackageList } from '#/api/system/tenant_package';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '套餐名',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2]))
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// 菜单权限
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
api: getTenantPackageList,
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
},
|
||||||
|
fieldName: 'pid',
|
||||||
|
label: $t('system.dept.parentDept'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: '开启', value: 1 },
|
||||||
|
{ label: '关闭', value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
maxLength: 50,
|
||||||
|
rows: 3,
|
||||||
|
showCount: true,
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50]))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemTenantPackageApi.SystemTenantPackage>,
|
||||||
|
): VxeTableGridOptions<SystemTenantPackageApi.SystemTenantPackage>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
title: '套餐名',
|
||||||
|
treeNode: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.dept.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
{
|
||||||
|
code: 'delete', // 默认的删除按钮
|
||||||
|
disabled: (row: SystemTenantPackageApi.SystemTenantPackage) => {
|
||||||
|
return !!(row.children && row.children.length > 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: '操作',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
135
web/apps/web-ele/src/views/system/tenant_package/list.vue
Normal file
135
web/apps/web-ele/src/views/system/tenant_package/list.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant_package';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
deleteTenantPackage,
|
||||||
|
getTenantPackageList,
|
||||||
|
} from '#/api/system/tenant_package';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑套餐
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新套餐
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除套餐
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteTenantPackage(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemTenantPackageApi.SystemTenantPackage>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (_params) => {
|
||||||
|
return await getTenantPackageList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="租户套餐">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.tenants_package.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant_package';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createTenantPackage, updateTenantPackage } from '#/api/system/tenant_package';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemTenantPackageApi.SystemTenantPackage>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.dept.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.dept.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateTenantPackage(formData.value.id, data)
|
||||||
|
: createTenantPackage(data));
|
||||||
|
modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemTenantPackagestApi.SystemTenantPackage>();
|
||||||
|
if (data) {
|
||||||
|
if (data.pid === 0) {
|
||||||
|
data.pid = undefined;
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form class="mx-4" />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
157
web/apps/web-ele/src/views/system/tenants/data.ts
Normal file
157
web/apps/web-ele/src/views/system/tenants/data.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantsApi } from '#/api/system/tenants';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'name',
|
||||||
|
label: $t('system.tenant.tenantName'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(
|
||||||
|
2,
|
||||||
|
$t('ui.formRules.minLength', [$t('system.tenant.tenantName'), 2]),
|
||||||
|
)
|
||||||
|
.max(
|
||||||
|
20,
|
||||||
|
$t('ui.formRules.maxLength', [$t('system.tenant.tenantName'), 20]),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: $t('common.enabled'), value: 1 },
|
||||||
|
{ label: $t('common.disabled'), value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
maxLength: 50,
|
||||||
|
rows: 3,
|
||||||
|
showCount: true,
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: $t('system.remark'),
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.max(50, $t('ui.formRules.maxLength', [$t('system.remark'), 50]))
|
||||||
|
.optional(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemTenantsApi.SystemTenants>,
|
||||||
|
): VxeTableGridOptions<SystemTenantsApi.SystemTenants>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
field: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
title: $t('system.tenant.tenantName'),
|
||||||
|
treeNode: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
// `contact_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系人',
|
||||||
|
// `contact_mobile` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系手机',
|
||||||
|
// `status` tinyint NOT NULL DEFAULT '0' COMMENT '租户状态(0正常 1停用)',
|
||||||
|
// `website` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '绑定域名',
|
||||||
|
// `package_id` bigint NOT NULL COMMENT '租户套餐编号',
|
||||||
|
// `expire_time` datetime NOT NULL COMMENT '过期时间',
|
||||||
|
// `account_count` int NOT NULL COMMENT '账号数量',
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'contact_name',
|
||||||
|
title: $t('system.tenant.contact_mobile'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'website',
|
||||||
|
title: $t('system.tenant.website'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'package_id',
|
||||||
|
title: $t('system.tenant.package_id'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'expire_time',
|
||||||
|
title: $t('system.tenant.expire_time'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'account_count',
|
||||||
|
title: $t('system.tenant.account_count'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: { name: 'CellTag' },
|
||||||
|
field: 'status',
|
||||||
|
title: $t('system.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: $t('system.createTime'),
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: $t('system.remark'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
{
|
||||||
|
code: 'delete', // 默认的删除按钮
|
||||||
|
disabled: (row: SystemTenantsApi.SystemTenants) => {
|
||||||
|
return !!(row.children && row.children.length > 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
headerAlign: 'center',
|
||||||
|
showOverflow: false,
|
||||||
|
title: $t('system.operation'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
131
web/apps/web-ele/src/views/system/tenants/list.vue
Normal file
131
web/apps/web-ele/src/views/system/tenants/list.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantsApi } from '#/api/system/tenants';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteTenants, getTenantsList } from '#/api/system/tenants';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑租户
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemTenantsApi.SystemTenants) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新租户
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除租户
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemTenantsApi.SystemTenants) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
deleteTenants(row.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemTenantsApi.SystemTenants>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async (_params) => {
|
||||||
|
return await getTenantsList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'pid',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="租户列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.tenants.name')]) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
78
web/apps/web-ele/src/views/system/tenants/modules/form.vue
Normal file
78
web/apps/web-ele/src/views/system/tenants/modules/form.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemTenantsApi } from '#/api/system/tenants';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createTenants, updateTenants } from '#/api/system/tenants';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemTenantsApi.SystemTenants>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.dept.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.dept.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateTenants(formData.value.id, data)
|
||||||
|
: createTenants(data));
|
||||||
|
modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemTenantstApi.SystemTenants>();
|
||||||
|
if (data) {
|
||||||
|
if (data.pid === 0) {
|
||||||
|
data.pid = undefined;
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form class="mx-4" />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<Button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
232
web/apps/web-ele/src/views/system/user/data.ts
Normal file
232
web/apps/web-ele/src/views/system/user/data.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemUserApi } from '#/models/system/user';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getDeptList, getRoleList } from '#/api/system';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemPostModel } from '#/models/system/post';
|
||||||
|
import { format_datetime } from '#/utils/date';
|
||||||
|
|
||||||
|
const systemPost = new SystemPostModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编辑表单的字段配置
|
||||||
|
*/
|
||||||
|
export function useSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'username',
|
||||||
|
label: '用户名',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['用户名']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['用户名', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'email',
|
||||||
|
label: '电子邮件地址',
|
||||||
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, $t('ui.formRules.required', ['电子邮件地址']))
|
||||||
|
.max(100, $t('ui.formRules.maxLength', ['电子邮件地址', 100])),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
multiple: true, // 允许多选
|
||||||
|
api: getDeptList,
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
},
|
||||||
|
fieldName: 'dept',
|
||||||
|
label: $t('system.dept.name'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
multiple: true, // 允许多选
|
||||||
|
mode: 'multiple', // 允许多选
|
||||||
|
api: getRoleList,
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
fieldName: 'role',
|
||||||
|
label: $t('system.role.name'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
multiple: true, // 允许多选
|
||||||
|
mode: 'multiple', // 允许多选
|
||||||
|
api: () => systemPost.list(),
|
||||||
|
class: 'w-full',
|
||||||
|
resultField: 'items',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
},
|
||||||
|
fieldName: 'post',
|
||||||
|
label: $t('system.post.name'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'mobile',
|
||||||
|
label: '手机号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'nickname',
|
||||||
|
label: '昵称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputPassword',
|
||||||
|
fieldName: 'password',
|
||||||
|
label: '密码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'city',
|
||||||
|
label: '城市',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'province',
|
||||||
|
label: '省份',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'country',
|
||||||
|
label: '国家',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
options: [
|
||||||
|
{ label: '开启', value: 1 },
|
||||||
|
{ label: '关闭', value: 0 },
|
||||||
|
],
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
defaultValue: 1,
|
||||||
|
fieldName: 'status',
|
||||||
|
label: $t('system.status'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: $t('system.remark'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表格列配置
|
||||||
|
* @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
|
||||||
|
* @param onActionClick 表格操作按钮点击事件
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useColumns(
|
||||||
|
onActionClick?: OnActionClickFn<SystemUserApi.SystemUser>,
|
||||||
|
): VxeTableGridOptions<SystemUserApi.SystemUser>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
title: '用户名',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'is_superuser',
|
||||||
|
title: '超级用户状态',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'date_joined',
|
||||||
|
title: '加入日期',
|
||||||
|
width: 150,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'mobile',
|
||||||
|
title: 'mobile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
},
|
||||||
|
field: 'status',
|
||||||
|
title: $t('system.status'),
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'login_ip',
|
||||||
|
title: 'login ip',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'last_login',
|
||||||
|
title: '最后登录',
|
||||||
|
width: 150,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: $t('system.remark'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'creator',
|
||||||
|
title: $t('system.creator'),
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'modifier',
|
||||||
|
title: $t('system.modifier'),
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'update_time',
|
||||||
|
title: $t('system.updateTime'),
|
||||||
|
width: 150,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_time',
|
||||||
|
title: $t('system.createTime'),
|
||||||
|
width: 150,
|
||||||
|
formatter: ({ cellValue }) => format_datetime(cellValue),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'center',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: $t('system.user.name'),
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
options: ['edit', 'delete'],
|
||||||
|
},
|
||||||
|
field: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
title: '操作',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
129
web/apps/web-ele/src/views/system/user/list.vue
Normal file
129
web/apps/web-ele/src/views/system/user/list.vue
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type {
|
||||||
|
OnActionClickParams,
|
||||||
|
VxeTableGridOptions,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import type { SystemUserApi } from '#/models/system/user';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
|
import { ElLoading, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemUserModel } from '#/models/system/user';
|
||||||
|
|
||||||
|
import { useColumns } from './data';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
const formModel = new SystemUserModel();
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑用户数据
|
||||||
|
*/
|
||||||
|
function onEdit(row: SystemUserApi.SystemUser) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新用户数据
|
||||||
|
*/
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户数据
|
||||||
|
*/
|
||||||
|
function onDelete(row: SystemUserApi.SystemUser) {
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
lock: true,
|
||||||
|
text: '删除用户数据',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
});
|
||||||
|
formModel
|
||||||
|
.delete(row.id)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
refreshGrid();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格操作按钮的回调函数
|
||||||
|
*/
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemUserApi.SystemUser>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridEvents: {},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await formModel.list({
|
||||||
|
page: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
custom: true,
|
||||||
|
export: false,
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
zoom: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格
|
||||||
|
*/
|
||||||
|
function refreshGrid() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="refreshGrid" />
|
||||||
|
<Grid table-title="用户数据">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<el-button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', [$t('system.user.name')]) }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
82
web/apps/web-ele/src/views/system/user/modules/form.vue
Normal file
82
web/apps/web-ele/src/views/system/user/modules/form.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemUserApi } from '#/models/system/user';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { SystemUserModel } from '#/models/system/user';
|
||||||
|
|
||||||
|
import { useSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formModel = new SystemUserModel();
|
||||||
|
|
||||||
|
const formData = ref<SystemUserApi.SystemUser>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', [$t('system.user.name')])
|
||||||
|
: $t('ui.actionTitle.create', [$t('system.user.name')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'horizontal',
|
||||||
|
commonConfig: {
|
||||||
|
colon: true,
|
||||||
|
formItemClass: 'col-span-2 md:col-span-1',
|
||||||
|
},
|
||||||
|
wrapperClass: 'grid-cols-2 gap-x-4',
|
||||||
|
schema: useSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
formApi.resetForm();
|
||||||
|
formApi.setValues(formData.value || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (valid) {
|
||||||
|
modalApi.lock();
|
||||||
|
const data = await formApi.getValues();
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? formModel.update(formData.value.id, data)
|
||||||
|
: formModel.create(data));
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<SystemUserApi.SystemUser>();
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
formApi.setValues(formData.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle" class="w-full max-w-[800px]">
|
||||||
|
<Form />
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex-auto">
|
||||||
|
<el-button type="primary" danger @click="resetForm">
|
||||||
|
{{ $t('common.reset') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped></style>
|
||||||
@@ -31,12 +31,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
loading: false,
|
loading: false,
|
||||||
qrCodeLoginPath: '/auth/qrcode-login',
|
qrCodeLoginPath: '/auth/qrcode-login',
|
||||||
registerPath: '/auth/register',
|
registerPath: '/auth/register',
|
||||||
showCodeLogin: true,
|
showCodeLogin: false,
|
||||||
showForgetPassword: true,
|
showForgetPassword: false,
|
||||||
showQrcodeLogin: true,
|
showQrcodeLogin: false,
|
||||||
showRegister: true,
|
showRegister: false,
|
||||||
showRememberMe: true,
|
showRememberMe: true,
|
||||||
showThirdPartyLogin: true,
|
showThirdPartyLogin: false,
|
||||||
submitButtonText: '',
|
submitButtonText: '',
|
||||||
subTitle: '',
|
subTitle: '',
|
||||||
title: '',
|
title: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user