添加对ele的支持,目前vben支持还不是特别完善
This commit is contained in:
@@ -4,10 +4,10 @@ VITE_PORT=5777
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/api
|
||||
VITE_GLOB_API_URL=http://127.0.0.1:8000/api
|
||||
|
||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||
VITE_NITRO_MOCK=true
|
||||
VITE_NITRO_MOCK=false
|
||||
|
||||
# 是否打开 devtools,true 为打开,false 为关闭
|
||||
VITE_DEVTOOLS=false
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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
|
||||
VITE_COMPRESS=none
|
||||
VITE_COMPRESS=gzip
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
@@ -7,6 +7,7 @@ import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
import { ElButton, ElImage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
import type {Recordable} from "@vben-core/typings";
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
@@ -66,5 +67,11 @@ setupVbenVxeTable({
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
@@ -22,14 +22,14 @@ export namespace AuthApi {
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
return requestClient.post<AuthApi.LoginResult>('/system/login/', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/system/refresh/', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export async function refreshTokenApi() {
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
return baseRequestClient.post('/system/logout/', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
@@ -47,5 +47,5 @@ export async function logoutApi() {
|
||||
* 获取用户权限码
|
||||
*/
|
||||
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() {
|
||||
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() {
|
||||
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 '@vben/styles';
|
||||
import '@vben/styles/ele';
|
||||
import ElementPlus from 'element-plus'
|
||||
|
||||
import { useTitle } from '@vueuse/core';
|
||||
import { ElLoading } from 'element-plus';
|
||||
|
||||
import { $t, setupI18n } from '#/locales';
|
||||
import { registerPermissionDirective } from '#/utils/permission';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import { initSetupVbenForm } from './adapter/form';
|
||||
@@ -51,7 +53,8 @@ async function bootstrap(namespace: string) {
|
||||
|
||||
// 安装权限指令
|
||||
registerAccessDirective(app);
|
||||
|
||||
// 注册自定义v-permission指令
|
||||
registerPermissionDirective(app);
|
||||
// 初始化 tippy
|
||||
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||
initTippy(app);
|
||||
@@ -62,6 +65,7 @@ async function bootstrap(namespace: string) {
|
||||
// 配置Motion插件
|
||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||
app.use(MotionPlugin);
|
||||
app.use(ElementPlus);
|
||||
|
||||
// 动态更新标题
|
||||
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
|
||||
app: {
|
||||
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 { $t } from '#/locales';
|
||||
|
||||
import { usePermissionStore } from './permission';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
@@ -63,7 +67,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
if (userInfo?.realName) {
|
||||
ElNotification({
|
||||
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
||||
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.username}`,
|
||||
title: $t('authentication.loginSuccess'),
|
||||
type: 'success',
|
||||
});
|
||||
@@ -102,6 +106,11 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
let userInfo: null | UserInfo = null;
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
// 设置权限
|
||||
if (userInfo && Array.isArray(userInfo.permissions)) {
|
||||
permissionStore.setPermissions(userInfo.permissions);
|
||||
}
|
||||
|
||||
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 { 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 { useAuthStore } from '#/store';
|
||||
@@ -24,7 +24,7 @@ const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'jack',
|
||||
value: 'chenze',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -57,7 +57,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
);
|
||||
if (findUser) {
|
||||
form.setValues({
|
||||
password: '123456',
|
||||
password: 'admin123',
|
||||
username: findUser.value,
|
||||
});
|
||||
}
|
||||
@@ -78,13 +78,13 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: markRaw(SliderCaptcha),
|
||||
fieldName: 'captcha',
|
||||
rules: z.boolean().refine((value) => value, {
|
||||
message: $t('authentication.verifyRequiredTip'),
|
||||
}),
|
||||
},
|
||||
// {
|
||||
// component: markRaw(SliderCaptcha),
|
||||
// fieldName: 'captcha',
|
||||
// rules: z.boolean().refine((value) => value, {
|
||||
// message: $t('authentication.verifyRequiredTip'),
|
||||
// }),
|
||||
// },
|
||||
];
|
||||
});
|
||||
</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,
|
||||
qrCodeLoginPath: '/auth/qrcode-login',
|
||||
registerPath: '/auth/register',
|
||||
showCodeLogin: true,
|
||||
showForgetPassword: true,
|
||||
showQrcodeLogin: true,
|
||||
showRegister: true,
|
||||
showCodeLogin: false,
|
||||
showForgetPassword: false,
|
||||
showQrcodeLogin: false,
|
||||
showRegister: false,
|
||||
showRememberMe: true,
|
||||
showThirdPartyLogin: true,
|
||||
showThirdPartyLogin: false,
|
||||
submitButtonText: '',
|
||||
subTitle: '',
|
||||
title: '',
|
||||
|
||||
Reference in New Issue
Block a user