100 changed files with 1713 additions and 3256 deletions
@ -1,36 +0,0 @@ |
|||||||
import type { GetUserInfoModel, LoginParams, LoginResultModel, SmsLoginParams } from './model/userModel' |
|
||||||
import { defHttp } from '@/utils/http/axios' |
|
||||||
|
|
||||||
import type { ErrorMessageMode } from '@/types/axios' |
|
||||||
|
|
||||||
enum Api { |
|
||||||
Login = '/system/auth/login', |
|
||||||
Logout = '/system/auth/logout', |
|
||||||
SmsLogin = '/system/auth/sms-login', |
|
||||||
GetUserInfo = '/system/auth/get-permission-info', |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @description: user login api |
|
||||||
*/ |
|
||||||
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { |
|
||||||
return defHttp.post<LoginResultModel>({ url: Api.Login, params }, { errorMessageMode: mode }) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @description: user smslogin api |
|
||||||
*/ |
|
||||||
export function smsLogin(params: SmsLoginParams, mode: ErrorMessageMode = 'modal') { |
|
||||||
return defHttp.post<LoginResultModel>({ url: Api.SmsLogin, params }, { errorMessageMode: mode }) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @description: getUserInfo |
|
||||||
*/ |
|
||||||
export function getUserInfo() { |
|
||||||
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' }) |
|
||||||
} |
|
||||||
|
|
||||||
export function doLogout() { |
|
||||||
return defHttp.post({ url: Api.Logout }) |
|
||||||
} |
|
@ -0,0 +1,29 @@ |
|||||||
|
import type { LoginParams, LoginResult, UserInfo } from './types' |
||||||
|
import { defHttp } from '@/utils/http/axios' |
||||||
|
import type { ErrorMessageMode } from '@/types/axios' |
||||||
|
|
||||||
|
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { |
||||||
|
return defHttp.post<LoginResult>({ |
||||||
|
url: '/auth/login', |
||||||
|
params, |
||||||
|
headers: { |
||||||
|
'tenant-id': '000000', |
||||||
|
}, |
||||||
|
}, { |
||||||
|
errorMessageMode: mode, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function getUserInfo() { |
||||||
|
return defHttp.get<UserInfo>({ |
||||||
|
url: '/system/permission/get-permission-info', |
||||||
|
}, { |
||||||
|
errorMessageMode: 'none', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function doLogout() { |
||||||
|
return defHttp.post({ |
||||||
|
url: '/auth/logout', |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import type { MenuItem } from '@/api/system/menu/types' |
||||||
|
|
||||||
|
export interface LoginParams { |
||||||
|
username: string |
||||||
|
password: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface LoginResult { |
||||||
|
userId: string |
||||||
|
accessToken: string |
||||||
|
refreshToken: string |
||||||
|
expiresTime: number |
||||||
|
} |
||||||
|
|
||||||
|
export interface UserInfo { |
||||||
|
user: User |
||||||
|
menus: MenuItem[] |
||||||
|
buttons: string[] |
||||||
|
} |
||||||
|
|
||||||
|
export interface User { |
||||||
|
id: string |
||||||
|
tenantId: string |
||||||
|
account: string |
||||||
|
realName: string |
||||||
|
avatar?: string |
||||||
|
email?: string |
||||||
|
mobile?: string |
||||||
|
remark?: string |
||||||
|
deptId: string |
||||||
|
roleId: string |
||||||
|
} |
@ -1,48 +1,36 @@ |
|||||||
|
import type { Department, LazyGetDeptListParams } from './types' |
||||||
import { defHttp } from '@/utils/http/axios' |
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
export interface DeptVO { |
export function lazyGetDeptList(params?: LazyGetDeptListParams) { |
||||||
id?: number |
return defHttp.get<Department[]>({ |
||||||
name: string |
url: '/system/dept/lazy-list', |
||||||
parentId: number |
params, |
||||||
status: number |
}) |
||||||
sort: number |
|
||||||
leaderUserId: number |
|
||||||
phone: string |
|
||||||
email: string |
|
||||||
createTime: Date |
|
||||||
} |
} |
||||||
|
|
||||||
export interface DeptPageReqVO { |
export function createDept(data: Partial<Department>) { |
||||||
name?: string |
return defHttp.post({ |
||||||
status?: number |
url: '/system/dept/save', |
||||||
|
data, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询部门(精简)列表
|
export function updateDept(data: Partial<Department>) { |
||||||
export function listSimpleDept() { |
return defHttp.post({ |
||||||
return defHttp.get({ url: '/system/dept/list-all-simple' }) |
url: '/system/dept/update', |
||||||
|
data, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询部门列表
|
export function deleteDept(id: string) { |
||||||
export function getDeptPage(params: DeptPageReqVO) { |
return defHttp.post({ |
||||||
return defHttp.get({ url: '/system/dept/list', params }) |
url: `/system/dept/delete?id=${id}`, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询部门详情
|
export function getDeptTree(params?: { tenantId: string }) { |
||||||
export function getDept(id: number) { |
return defHttp.get<{ id: string, title: string }[]>({ |
||||||
return defHttp.get({ url: `/system/dept/get?id=${id}` }) |
url: '/system/dept/tree', |
||||||
} |
params, |
||||||
|
}) |
||||||
// 新增部门
|
|
||||||
export function createDept(data: DeptVO) { |
|
||||||
return defHttp.post({ url: '/system/dept/create', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 修改部门
|
|
||||||
export function updateDept(params: DeptVO) { |
|
||||||
return defHttp.put({ url: '/system/dept/update', data: params }) |
|
||||||
} |
|
||||||
|
|
||||||
// 删除部门
|
|
||||||
export function deleteDept(id: number) { |
|
||||||
return defHttp.delete({ url: `/system/dept/delete?id=${id}` }) |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,17 @@ |
|||||||
|
export interface Department { |
||||||
|
id: string |
||||||
|
tenantId: string |
||||||
|
parentId: string |
||||||
|
deptName: string |
||||||
|
parentName: string |
||||||
|
deptTypeName: string |
||||||
|
children: Department[] |
||||||
|
hasChildren: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export interface LazyGetDeptListParams { |
||||||
|
id?: string |
||||||
|
tenantId?: string |
||||||
|
parentId?: string |
||||||
|
deptName?: string |
||||||
|
} |
@ -1,52 +1,41 @@ |
|||||||
|
import type { GetMenuListParams, MenuItem } from './types' |
||||||
import { defHttp } from '@/utils/http/axios' |
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
export interface MenuVO { |
export function getMenuListWithoutButtons() { |
||||||
id: number |
return defHttp.get<MenuItem[]>({ |
||||||
name: string |
url: '/system/menu/tree', |
||||||
permission: string |
}) |
||||||
type: number |
|
||||||
sort: number |
|
||||||
parentId: number |
|
||||||
path: string |
|
||||||
icon: string |
|
||||||
component: string |
|
||||||
status: number |
|
||||||
visible: boolean |
|
||||||
keepAlive: boolean |
|
||||||
createTime: Date |
|
||||||
} |
} |
||||||
|
|
||||||
export interface MenuPageReqVO { |
export function getMenuList(params: GetMenuListParams) { |
||||||
name?: string |
return defHttp.get<PageResult<MenuItem>>({ |
||||||
status?: number |
url: '/system/menu/list', |
||||||
|
params, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询菜单(精简)列表
|
export function getMenu(id: string) { |
||||||
export function listSimpleMenus() { |
return defHttp.get({ |
||||||
return defHttp.get({ url: '/system/menu/list-all-simple' }) |
url: `/system/menu/get?id=${id}`, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询菜单列表
|
export function createMenu(data: Omit<MenuItem, 'id' | 'children'>) { |
||||||
export function getMenuList(params: MenuPageReqVO) { |
return defHttp.post({ |
||||||
return defHttp.get({ url: '/system/menu/list', params }) |
url: '/system/menu/save', |
||||||
|
data, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 获取菜单详情
|
export function updateMenu(data: Omit<MenuItem, 'children'>) { |
||||||
export function getMenu(id: number) { |
return defHttp.post({ |
||||||
return defHttp.get({ url: `/system/menu/get?id=${id}` }) |
url: '/system/menu/update', |
||||||
|
data, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 新增菜单
|
export function deleteMenu(id: string) { |
||||||
export function createMenu(data: MenuVO) { |
return defHttp.post({ |
||||||
return defHttp.post({ url: '/system/menu/create', data }) |
url: `/system/menu/delete?id=${id}`, |
||||||
} |
}) |
||||||
|
|
||||||
// 修改菜单
|
|
||||||
export function updateMenu(data: MenuVO) { |
|
||||||
return defHttp.put({ url: '/system/menu/update', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 删除菜单
|
|
||||||
export function deleteMenu(id: number) { |
|
||||||
return defHttp.delete({ url: `/system/menu/delete?id=${id}` }) |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,26 @@ |
|||||||
|
export interface GetMenuListParams { |
||||||
|
name?: string |
||||||
|
code?: string |
||||||
|
} |
||||||
|
|
||||||
|
export enum SystemMenuTypeEnum { |
||||||
|
DIR = 0, // 目录
|
||||||
|
MENU = 1, // 菜单
|
||||||
|
BUTTON = 2, // 按钮
|
||||||
|
} |
||||||
|
|
||||||
|
export interface MenuItem { |
||||||
|
id: string |
||||||
|
parentId: string |
||||||
|
name: string |
||||||
|
component: string |
||||||
|
componentName?: string |
||||||
|
path: string |
||||||
|
icon: string |
||||||
|
sort: number |
||||||
|
children: MenuItem[] |
||||||
|
code: string |
||||||
|
type: SystemMenuTypeEnum |
||||||
|
visible: BooleanFlag |
||||||
|
keepAlive: BooleanFlag |
||||||
|
} |
@ -1,70 +1,56 @@ |
|||||||
|
import type { GetRoleListParams, MenuTreeNode, Role } from './types' |
||||||
import { defHttp } from '@/utils/http/axios' |
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
export interface RoleVO { |
export function lazyGetRoleList(params: GetRoleListParams) { |
||||||
id: number |
return defHttp.get<Role[]>({ |
||||||
name: string |
url: '/system/role/lazy-list', |
||||||
code: string |
params, |
||||||
sort: number |
}) |
||||||
status: number |
|
||||||
type: number |
|
||||||
createTime: Date |
|
||||||
} |
} |
||||||
|
|
||||||
export interface RolePageReqVO extends PageParam { |
export function createRole(data: Partial<Role>) { |
||||||
name?: string |
return defHttp.post({ |
||||||
code?: string |
url: '/system/role/save', |
||||||
status?: number |
data, |
||||||
createTime?: Date[] |
}) |
||||||
} |
} |
||||||
|
|
||||||
export interface UpdateStatusReqVO { |
export function updateRole(data: Partial<Role>) { |
||||||
id: number |
return defHttp.post({ |
||||||
status: number |
url: '/system/role/update', |
||||||
|
data, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
export interface RoleExportReqVO { |
export function deleteRole(id: string) { |
||||||
name?: string |
return defHttp.post({ |
||||||
code?: string |
url: `/system/role/delete?id=${id}`, |
||||||
status?: number |
}) |
||||||
createTime?: Date[] |
|
||||||
} |
} |
||||||
|
|
||||||
// 查询角色列表
|
export function getRoleTree(params?: { tenantId: string }) { |
||||||
export function getRolePage(params: RolePageReqVO) { |
return defHttp.get<{ id: string, title: string }[]>({ |
||||||
return defHttp.get({ url: '/system/role/page', params }) |
url: '/system/role/tree', |
||||||
|
params, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询角色(精简)列表
|
export function getMenuTree() { |
||||||
export function listSimpleRoles() { |
return defHttp.get<MenuTreeNode[]>({ |
||||||
return defHttp.get({ url: '/system/role/list-all-simple' }) |
url: '/system/menu/grant-tree', |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询角色详情
|
export function getMenuIdsByRole(roleId: string) { |
||||||
export function getRole(id: number) { |
return defHttp.get<string[]>({ |
||||||
return defHttp.get({ url: `/system/role/get?id=${id}` }) |
url: '/system/permission/list-role-menus', |
||||||
|
params: { roleId }, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 新增角色
|
export function assignMenuToRole(data: { roleId: string, menuIds: string[] }) { |
||||||
export function createRole(data: RoleVO) { |
return defHttp.post({ |
||||||
return defHttp.post({ url: '/system/role/create', data }) |
url: '/system/permission/assign-role-menu', |
||||||
} |
data, |
||||||
|
}) |
||||||
// 修改角色
|
|
||||||
export function updateRole(data: RoleVO) { |
|
||||||
return defHttp.put({ url: '/system/role/update', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 修改角色状态
|
|
||||||
export function updateRoleStatus(data: UpdateStatusReqVO) { |
|
||||||
return defHttp.put({ url: '/system/role/update-status', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 删除角色
|
|
||||||
export function deleteRole(id: number) { |
|
||||||
return defHttp.delete({ url: `/system/role/delete?id=${id}` }) |
|
||||||
} |
|
||||||
|
|
||||||
// 导出角色
|
|
||||||
export function exportRole(params: RoleExportReqVO) { |
|
||||||
return defHttp.download({ url: '/system/post/export', params }, '导出角色.xls') |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,29 @@ |
|||||||
|
export interface GetRoleListParams extends PageParam { |
||||||
|
tenantId?: string |
||||||
|
parentId?: string |
||||||
|
roleName?: string |
||||||
|
roleAlias?: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface Role { |
||||||
|
id: string |
||||||
|
tenantId: string |
||||||
|
tenantName: string |
||||||
|
parentId: string |
||||||
|
roleName: string |
||||||
|
roleAlias: string |
||||||
|
createTime?: string |
||||||
|
createUser?: string |
||||||
|
createDept?: string |
||||||
|
updateTime?: string |
||||||
|
updateUser?: string |
||||||
|
hasChildren?: boolean |
||||||
|
children?: Role[] |
||||||
|
} |
||||||
|
|
||||||
|
export interface MenuTreeNode { |
||||||
|
id: string |
||||||
|
parentId: string |
||||||
|
title: string |
||||||
|
children?: MenuTreeNode[] |
||||||
|
} |
@ -1,62 +1,35 @@ |
|||||||
|
import type { GetTenantListParams, Tenant } from './types' |
||||||
import { defHttp } from '@/utils/http/axios' |
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
export interface TenantVO { |
export function getTenantList(params: GetTenantListParams) { |
||||||
id: number |
return defHttp.get<PageResult<Tenant>>({ |
||||||
name: string |
url: '/system/tenant/page', |
||||||
contactName: string |
params, |
||||||
contactMobile: string |
}) |
||||||
status: number |
|
||||||
domain: string |
|
||||||
packageId: number |
|
||||||
username: string |
|
||||||
password: string |
|
||||||
expireTime: Date |
|
||||||
accountCount: number |
|
||||||
createTime: Date |
|
||||||
} |
} |
||||||
|
|
||||||
export interface TenantPageReqVO extends PageParam { |
export function updateTenant(data: Tenant) { |
||||||
name?: string |
return defHttp.post({ |
||||||
contactName?: string |
url: '/system/tenant/update', |
||||||
contactMobile?: string |
data, |
||||||
status?: number |
}) |
||||||
createTime?: Date[] |
|
||||||
} |
} |
||||||
|
|
||||||
export interface TenantExportReqVO { |
export function createTenant(data: Tenant) { |
||||||
name?: string |
return defHttp.post({ |
||||||
contactName?: string |
url: '/system/tenant/save', |
||||||
contactMobile?: string |
data, |
||||||
status?: number |
}) |
||||||
createTime?: Date[] |
|
||||||
} |
} |
||||||
|
|
||||||
// 查询租户列表
|
export function deleteTenant(id: string) { |
||||||
export function getTenantPage(params: TenantPageReqVO) { |
return defHttp.post({ |
||||||
return defHttp.get({ url: '/system/tenant/page', params }) |
url: `/system/tenant/remove?id=${id}`, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询租户详情
|
export function getAllTenants() { |
||||||
export function getTenant(id: number) { |
return defHttp.get({ |
||||||
return defHttp.get({ url: `/system/tenant/get?id=${id}` }) |
url: '/system/tenant/select', |
||||||
} |
}) |
||||||
|
|
||||||
// 新增租户
|
|
||||||
export function createTenant(data: TenantVO) { |
|
||||||
return defHttp.post({ url: '/system/tenant/create', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 修改租户
|
|
||||||
export function updateTenant(data: TenantVO) { |
|
||||||
return defHttp.put({ url: '/system/tenant/update', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 删除租户
|
|
||||||
export function deleteTenant(id: number) { |
|
||||||
return defHttp.delete({ url: `/system/tenant/delete?id=${id}` }) |
|
||||||
} |
|
||||||
|
|
||||||
// 导出租户
|
|
||||||
export function exportTenant(params: TenantExportReqVO) { |
|
||||||
return defHttp.download({ url: '/system/tenant/export-excel', params }, '租户.xls') |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
export interface Tenant { |
||||||
|
id: string |
||||||
|
tenantId?: string |
||||||
|
tenantName: string |
||||||
|
adminAccount: string |
||||||
|
contactName: string |
||||||
|
contactMobile: string |
||||||
|
expireTime?: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface GetTenantListParams extends PageParam { |
||||||
|
tenantId?: string |
||||||
|
tenantName?: string |
||||||
|
contactName?: string |
||||||
|
contactMobile?: string |
||||||
|
} |
@ -1,91 +1,29 @@ |
|||||||
|
import type { GetUserListParams, SystemUser } from './types' |
||||||
import { defHttp } from '@/utils/http/axios' |
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
export interface UserVO { |
export function getUserList(params: GetUserListParams) { |
||||||
id: number |
return defHttp.get<PageResult<SystemUser>>({ |
||||||
username: string |
url: '/system/user/page', |
||||||
nickname: string |
params, |
||||||
deptId: number |
}) |
||||||
postIds: string[] |
|
||||||
email: string |
|
||||||
mobile: string |
|
||||||
sex: number |
|
||||||
avatar: string |
|
||||||
loginIp: string |
|
||||||
status: number |
|
||||||
remark: string |
|
||||||
loginDate: Date |
|
||||||
createTime: Date |
|
||||||
} |
} |
||||||
|
|
||||||
export interface UserPageReqVO extends PageParam { |
export function createUser(data: Partial<SystemUser>) { |
||||||
deptId?: number |
return defHttp.post({ |
||||||
username?: string |
url: '/system/user/save', |
||||||
mobile?: string |
data, |
||||||
status?: number |
}) |
||||||
createTime?: Date[] |
|
||||||
} |
} |
||||||
|
|
||||||
export interface UserExportReqVO { |
export function updateUser(data: Partial<SystemUser>) { |
||||||
code?: string |
return defHttp.post({ |
||||||
name?: string |
url: '/system/user/update', |
||||||
status?: number |
data, |
||||||
createTime?: Date[] |
}) |
||||||
} |
} |
||||||
|
|
||||||
// 查询用户管理列表
|
export function deleteUser(id: string) { |
||||||
export function getUserPage(params: UserPageReqVO) { |
return defHttp.post({ |
||||||
return defHttp.get({ url: '/system/user/page', params }) |
url: `/system/user/delete?id=${id}`, |
||||||
} |
}) |
||||||
|
|
||||||
// 查询用户详情
|
|
||||||
export function getUser(id: number) { |
|
||||||
return defHttp.get({ url: `/system/user/get?id=${id}` }) |
|
||||||
} |
|
||||||
|
|
||||||
// 新增用户
|
|
||||||
export function createUser(data: UserVO) { |
|
||||||
return defHttp.post({ url: '/system/user/create', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 修改用户
|
|
||||||
export function updateUser(data: UserVO) { |
|
||||||
return defHttp.put({ url: '/system/user/update', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 删除用户
|
|
||||||
export function deleteUser(id: number) { |
|
||||||
return defHttp.delete({ url: `/system/user/delete?id=${id}` }) |
|
||||||
} |
|
||||||
|
|
||||||
// 导出用户
|
|
||||||
export function exportUser(params: UserExportReqVO) { |
|
||||||
return defHttp.download({ url: '/system/user/export', params }, '用户.xls') |
|
||||||
} |
|
||||||
|
|
||||||
// 下载用户导入模板
|
|
||||||
export function importUserTemplate() { |
|
||||||
return defHttp.download({ url: '/system/user/get-import-template' }, '用户导入模板.xls') |
|
||||||
} |
|
||||||
|
|
||||||
// 用户密码重置
|
|
||||||
export function resetUserPwd(id: number, password: string) { |
|
||||||
const data = { |
|
||||||
id, |
|
||||||
password, |
|
||||||
} |
|
||||||
return defHttp.put({ url: '/system/user/update-password', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 用户状态修改
|
|
||||||
export function updateUserStatus(id: number, status: number) { |
|
||||||
const data = { |
|
||||||
id, |
|
||||||
status, |
|
||||||
} |
|
||||||
return defHttp.put({ url: '/system/user/update-status', data }) |
|
||||||
} |
|
||||||
|
|
||||||
// 获取用户精简信息列表
|
|
||||||
export function getListSimpleUsers() { |
|
||||||
return defHttp.get({ url: '/system/user/list-all-simple' }) |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,23 @@ |
|||||||
|
export interface GetUserListParams extends PageParam { |
||||||
|
tenantId?: string |
||||||
|
deptId?: string |
||||||
|
account?: string |
||||||
|
mobile?: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface SystemUser { |
||||||
|
id: string |
||||||
|
account: string |
||||||
|
avatar: string |
||||||
|
deptId: string |
||||||
|
deptName: string |
||||||
|
email: string |
||||||
|
mobile: string |
||||||
|
realName: string |
||||||
|
remark: string |
||||||
|
roleId: string |
||||||
|
roleName: string |
||||||
|
sex: 1 | 2 | 3 |
||||||
|
tenantId: string |
||||||
|
tenantName: string |
||||||
|
} |
@ -1,155 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { computed, reactive, ref, unref } from 'vue' |
|
||||||
import { Form, Input } from 'ant-design-vue' |
|
||||||
import LoginFormTitle from './LoginFormTitle.vue' |
|
||||||
import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin' |
|
||||||
import { CountdownInput } from '@/components/CountDown' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { useUserStore } from '@/store/modules/user' |
|
||||||
import { usePermissionStore } from '@/store/modules/permission' |
|
||||||
import { useGlobSetting } from '@/hooks/setting' |
|
||||||
import { useDesign } from '@/hooks/web/useDesign' |
|
||||||
import * as authUtil from '@/utils/auth' |
|
||||||
|
|
||||||
import { Verify } from '@/components/Verifition' |
|
||||||
import { getTenantIdByName, sendSmsCode } from '@/api/base/login' |
|
||||||
|
|
||||||
const FormItem = Form.Item |
|
||||||
|
|
||||||
const { t } = useI18n() |
|
||||||
const { prefixCls } = useDesign('login') |
|
||||||
const { createMessage, notification, createErrorModal } = useMessage() |
|
||||||
const { handleBackLogin, getLoginState } = useLoginState() |
|
||||||
const { tenantEnable, captchaEnable } = useGlobSetting() |
|
||||||
const { getFormRules } = useFormRules() |
|
||||||
const userStore = useUserStore() |
|
||||||
const permissionStore = usePermissionStore() |
|
||||||
|
|
||||||
const formRef = ref() |
|
||||||
const loading = ref(false) |
|
||||||
|
|
||||||
const mobileCodeTimer = ref(0) |
|
||||||
const scene = ref(21) |
|
||||||
|
|
||||||
const verify = ref() |
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 |
|
||||||
|
|
||||||
const formData = reactive({ |
|
||||||
tenantName: '芋道源码', |
|
||||||
mobile: '', |
|
||||||
mobileCode: '', |
|
||||||
captchaVerification: '', |
|
||||||
}) |
|
||||||
|
|
||||||
const { validForm } = useFormValid(formRef) |
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE) |
|
||||||
|
|
||||||
// 获取验证码 |
|
||||||
async function getCode() { |
|
||||||
// 情况一,未开启:则直接登录 |
|
||||||
if (captchaEnable === 'false') { |
|
||||||
await handleLogin() |
|
||||||
} |
|
||||||
else { |
|
||||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录 |
|
||||||
// 弹出验证码 |
|
||||||
verify.value.show() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 获取租户ID |
|
||||||
async function getTenantId() { |
|
||||||
if (tenantEnable === 'true') { |
|
||||||
const res = await getTenantIdByName(formData.tenantName) |
|
||||||
authUtil.setTenantId(res) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async function handleLogin() { |
|
||||||
const data = await validForm() |
|
||||||
if (!data) |
|
||||||
return |
|
||||||
try { |
|
||||||
loading.value = true |
|
||||||
const userInfo = await userStore.smsLogin({ |
|
||||||
mobile: data.mobile, |
|
||||||
code: data.mobileCode, |
|
||||||
mode: 'none', // 不要默认的错误提示 |
|
||||||
}) |
|
||||||
if (userInfo) { |
|
||||||
await permissionStore.changePermissionCode(userInfo.permissions) |
|
||||||
notification.success({ |
|
||||||
message: t('sys.login.loginSuccessTitle'), |
|
||||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.nickname}`, |
|
||||||
duration: 3, |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
catch (error) { |
|
||||||
createErrorModal({ |
|
||||||
title: t('sys.api.errorTip'), |
|
||||||
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'), |
|
||||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body, |
|
||||||
}) |
|
||||||
} |
|
||||||
finally { |
|
||||||
loading.value = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async function getSmsCode() { |
|
||||||
await getTenantId() |
|
||||||
if (mobileCodeTimer.value > 0) |
|
||||||
return |
|
||||||
const data = await validForm() |
|
||||||
if (!data) |
|
||||||
return |
|
||||||
const res = await sendSmsCode(formData.mobile, scene.value) |
|
||||||
if (res) { |
|
||||||
createMessage.success(t('common.successText')) |
|
||||||
mobileCodeTimer.value = 60 |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div v-if="getShow"> |
|
||||||
<LoginFormTitle class="enter-x" /> |
|
||||||
<Form ref="formRef" class="enter-x p-4" :model="formData" :rules="getFormRules"> |
|
||||||
<FormItem name="tenantName" class="enter-x"> |
|
||||||
<Input |
|
||||||
v-if="tenantEnable === 'true'" |
|
||||||
v-model:value="formData.tenantName" |
|
||||||
size="large" |
|
||||||
:placeholder="t('sys.login.tenantName')" |
|
||||||
class="fix-auto-fill" |
|
||||||
/> |
|
||||||
</FormItem> |
|
||||||
<FormItem name="mobile" class="enter-x"> |
|
||||||
<Input v-model:value="formData.mobile" size="large" :placeholder="t('sys.login.mobile')" class="fix-auto-fill" /> |
|
||||||
</FormItem> |
|
||||||
<FormItem name="mobileCode" class="enter-x"> |
|
||||||
<CountdownInput |
|
||||||
v-model:value="formData.mobileCode" |
|
||||||
size="large" |
|
||||||
class="fix-auto-fill" |
|
||||||
:count="mobileCodeTimer" |
|
||||||
:send-code-api="getSmsCode" |
|
||||||
:placeholder="t('sys.login.smsCode')" |
|
||||||
/> |
|
||||||
</FormItem> |
|
||||||
|
|
||||||
<FormItem class="enter-x"> |
|
||||||
<a-button type="primary" size="large" block :loading="loading" @click="getCode"> |
|
||||||
{{ t('sys.login.loginButton') }} |
|
||||||
</a-button> |
|
||||||
<a-button size="large" block class="mt-4" @click="handleBackLogin"> |
|
||||||
{{ t('sys.login.backSignIn') }} |
|
||||||
</a-button> |
|
||||||
</FormItem> |
|
||||||
</Form> |
|
||||||
<Verify ref="verify" mode="pop" :captcha-type="captchaType" :img-size="{ width: '400px', height: '200px' }" @success="handleLogin" /> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,37 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { computed, unref } from 'vue' |
|
||||||
import { Divider, Popover, QRCode } from 'ant-design-vue' |
|
||||||
import LoginFormTitle from './LoginFormTitle.vue' |
|
||||||
import { LoginStateEnum, useLoginState } from './useLogin' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
|
|
||||||
import loginImg from '@/assets/images/logo.png' |
|
||||||
|
|
||||||
// Login QR code |
|
||||||
const qrCodeUrl = '' |
|
||||||
|
|
||||||
const { t } = useI18n() |
|
||||||
const { handleBackLogin, getLoginState } = useLoginState() |
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div v-if="getShow"> |
|
||||||
<LoginFormTitle class="enter-x" /> |
|
||||||
<div class="enter-x min-h-64 min-w-64"> |
|
||||||
<Popover :overlay-inner-style="{ padding: 0 }"> |
|
||||||
<template #content> |
|
||||||
<QRCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" :bordered="false" /> |
|
||||||
</template> |
|
||||||
<img width="100" height="100" :src="loginImg"> |
|
||||||
</Popover> |
|
||||||
<Divider class="enter-x"> |
|
||||||
{{ t('sys.login.scanSign') }} |
|
||||||
</Divider> |
|
||||||
<a-button size="large" block class="enter-x mt-4" @click="handleBackLogin"> |
|
||||||
{{ t('sys.login.backSignIn') }} |
|
||||||
</a-button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -1,199 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { onMounted, reactive, ref } from 'vue' |
|
||||||
import { useRoute } from 'vue-router' |
|
||||||
import { Checkbox, Col, Form, Row } from 'ant-design-vue' |
|
||||||
|
|
||||||
import { useFormValid, useLoginState } from './useLogin' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign' |
|
||||||
import { authorize, getAuthorize } from '@/api/base/login' |
|
||||||
|
|
||||||
const FormItem = Form.Item |
|
||||||
|
|
||||||
const { t } = useI18n() |
|
||||||
const { query } = useRoute() |
|
||||||
const { notification, createErrorModal } = useMessage() |
|
||||||
const { prefixCls } = useDesign('login') |
|
||||||
|
|
||||||
const { handleBackLogin } = useLoginState() |
|
||||||
|
|
||||||
const formRef = ref() |
|
||||||
const loading = ref(false) |
|
||||||
|
|
||||||
const loginForm = reactive({ |
|
||||||
scopes: [] as any[], // 已选中的 scope 数组 |
|
||||||
}) |
|
||||||
|
|
||||||
// URL 上的 client_id、scope 等参数 |
|
||||||
const params = reactive({ |
|
||||||
responseType: undefined as any, |
|
||||||
clientId: undefined as any, |
|
||||||
redirectUri: undefined as any, |
|
||||||
state: undefined as any, |
|
||||||
scopes: [] as any[], // 优先从 query 参数获取;如果未传递,从后端获取 |
|
||||||
}) |
|
||||||
|
|
||||||
// 客户端信息 |
|
||||||
let client = reactive({ |
|
||||||
name: '', |
|
||||||
logo: '', |
|
||||||
}) |
|
||||||
|
|
||||||
const { validForm } = useFormValid(formRef) |
|
||||||
|
|
||||||
async function init() { |
|
||||||
// 解析参数 |
|
||||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write |
|
||||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read |
|
||||||
params.responseType = query.response_type as any |
|
||||||
params.clientId = query.client_id as any |
|
||||||
params.redirectUri = query.redirect_uri as any |
|
||||||
params.state = query.state as any |
|
||||||
if (query.scope) |
|
||||||
params.scopes = (query.scope as any).split(' ') |
|
||||||
|
|
||||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。 |
|
||||||
if (params.scopes.length > 0) { |
|
||||||
const res = await doAuthorize(true, params.scopes, []) |
|
||||||
const href = res |
|
||||||
if (!href) { |
|
||||||
console.log('自动授权未通过!') |
|
||||||
return |
|
||||||
} |
|
||||||
location.href = href |
|
||||||
} |
|
||||||
|
|
||||||
// 获取授权页的基本信息 |
|
||||||
const res = await getAuthorize(params.clientId) |
|
||||||
client = res.client |
|
||||||
// 解析 scope |
|
||||||
let scopes |
|
||||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes |
|
||||||
if (params.scopes.length > 0) { |
|
||||||
scopes = [] |
|
||||||
for (const scope of res.scopes) { |
|
||||||
if (params.scopes.includes(scope.key)) |
|
||||||
scopes.push(scope) |
|
||||||
} |
|
||||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它 |
|
||||||
} |
|
||||||
else { |
|
||||||
scopes = res.scopes |
|
||||||
for (const scope of scopes) |
|
||||||
params.scopes.push(scope.key) |
|
||||||
} |
|
||||||
// 生成已选中的 checkedScopes |
|
||||||
for (const scope of scopes) { |
|
||||||
if (scope.value) |
|
||||||
loginForm.scopes.push(scope.key) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async function handleAuthorize(approved) { |
|
||||||
const data = await validForm() |
|
||||||
if (!data) |
|
||||||
return |
|
||||||
try { |
|
||||||
loading.value = true |
|
||||||
// 计算 checkedScopes + uncheckedScopes |
|
||||||
let checkedScopes |
|
||||||
let uncheckedScopes |
|
||||||
if (approved) { |
|
||||||
// 同意授权,按照用户的选择 |
|
||||||
checkedScopes = loginForm.scopes |
|
||||||
uncheckedScopes = params.scopes.filter(item => !checkedScopes.includes(item)) |
|
||||||
} |
|
||||||
else { |
|
||||||
// 拒绝,则都是取消 |
|
||||||
checkedScopes = [] |
|
||||||
uncheckedScopes = params.scopes |
|
||||||
} |
|
||||||
// 提交授权的请求 |
|
||||||
const res = await doAuthorize(false, checkedScopes, uncheckedScopes) |
|
||||||
if (res) { |
|
||||||
const href = res |
|
||||||
if (!href) |
|
||||||
return |
|
||||||
|
|
||||||
location.href = href |
|
||||||
notification.success({ |
|
||||||
message: t('sys.login.loginSuccessTitle'), |
|
||||||
description: `${t('sys.login.loginSuccessDesc')}`, |
|
||||||
duration: 3, |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
catch (error) { |
|
||||||
createErrorModal({ |
|
||||||
title: t('sys.api.errorTip'), |
|
||||||
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'), |
|
||||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body, |
|
||||||
}) |
|
||||||
} |
|
||||||
finally { |
|
||||||
loading.value = false |
|
||||||
} |
|
||||||
} |
|
||||||
async function doAuthorize(autoApprove, checkedScopes, uncheckedScopes) { |
|
||||||
return await authorize( |
|
||||||
params.responseType, |
|
||||||
params.clientId, |
|
||||||
params.redirectUri, |
|
||||||
params.state, |
|
||||||
autoApprove, |
|
||||||
checkedScopes, |
|
||||||
uncheckedScopes, |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
function formatScope(scope) { |
|
||||||
// 格式化 scope 授权范围,方便用户理解。 |
|
||||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。 |
|
||||||
switch (scope) { |
|
||||||
case 'user.read': |
|
||||||
return t('sys.login.ssoInfoDesc') |
|
||||||
case 'user.write': |
|
||||||
return t('sys.login.ssoEditDesc') |
|
||||||
default: |
|
||||||
return scope |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
onMounted(() => { |
|
||||||
init() |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-left xl:text-3xl"> |
|
||||||
{{ client.name + t('sys.login.ssoSignInFormTitle') }} |
|
||||||
</h2> |
|
||||||
<Form ref="formRef" class="enter-x p-4" :model="loginForm" @keypress.enter="handleAuthorize(true)"> |
|
||||||
此第三方应用请求获取以下权限: |
|
||||||
<Row class="enter-x"> |
|
||||||
<Col :span="12"> |
|
||||||
<template v-for="scope in params.scopes" :key="scope"> |
|
||||||
<FormItem> |
|
||||||
<!-- No logic, you need to deal with it yourself --> |
|
||||||
<Checkbox :checked="scope" size="small"> |
|
||||||
<a-button type="link" size="small"> |
|
||||||
{{ formatScope(scope) }} |
|
||||||
</a-button> |
|
||||||
</Checkbox> |
|
||||||
</FormItem> |
|
||||||
</template> |
|
||||||
</Col> |
|
||||||
</Row> |
|
||||||
|
|
||||||
<FormItem class="enter-x"> |
|
||||||
<a-button type="primary" size="large" block :loading="loading" @click="handleAuthorize(true)"> |
|
||||||
{{ t('sys.login.loginButton') }} |
|
||||||
</a-button> |
|
||||||
<a-button size="large" class="enter-x mt-4" block @click="handleBackLogin"> |
|
||||||
{{ t('common.cancelText') }} |
|
||||||
</a-button> |
|
||||||
</FormItem> |
|
||||||
</Form> |
|
||||||
</template> |
|
@ -1,200 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { computed } from 'vue' |
|
||||||
import SSOForm from './SSOForm.vue' |
|
||||||
import { AppDarkModeToggle, AppLocalePicker, AppLogo } from '@/components/Application' |
|
||||||
import { useGlobSetting } from '@/hooks/setting' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useDesign } from '@/hooks/web/useDesign' |
|
||||||
import { useLocaleStore } from '@/store/modules/locale' |
|
||||||
|
|
||||||
defineProps({ |
|
||||||
sessionTimeout: { |
|
||||||
type: Boolean, |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const globSetting = useGlobSetting() |
|
||||||
const { prefixCls } = useDesign('login') |
|
||||||
const { t } = useI18n() |
|
||||||
const localeStore = useLocaleStore() |
|
||||||
const showLocale = localeStore.getShowPicker |
|
||||||
const title = computed(() => globSetting?.title ?? '') |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div :class="prefixCls" class="relative h-full w-full px-4"> |
|
||||||
<div class="absolute right-4 top-4 flex items-center"> |
|
||||||
<AppDarkModeToggle v-if="!sessionTimeout" class="enter-x mr-2" /> |
|
||||||
<AppLocalePicker v-if="!sessionTimeout && showLocale" class="enter-x text-white xl:text-gray-600" :show-text="false" /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<span class="-enter-x xl:hidden"> |
|
||||||
<AppLogo :always-show-title="true" /> |
|
||||||
</span> |
|
||||||
|
|
||||||
<div class="relative mx-auto h-full py-2 container sm:px-10"> |
|
||||||
<div class="h-full flex"> |
|
||||||
<div class="mr-4 hidden min-h-full pl-4 xl:w-6/12 xl:flex xl:flex-col"> |
|
||||||
<AppLogo class="-enter-x" /> |
|
||||||
<div class="my-auto"> |
|
||||||
<img :alt="title" src="@/assets/svg/login-box-bg.svg" class="-enter-x w-1/2 -mt-16"> |
|
||||||
<div class="-enter-x mt-10 text-white font-medium"> |
|
||||||
<span class="mt-4 inline-block text-3xl"> {{ t('sys.login.signInTitle') }}</span> |
|
||||||
</div> |
|
||||||
<div class="-enter-x mt-5 text-white font-normal dark:text-gray-500"> |
|
||||||
{{ t('sys.login.signInDesc') }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<div class="h-full w-full flex py-5 xl:my-0 xl:h-auto xl:w-6/12 xl:py-0"> |
|
||||||
<!-- eslint-disable max-len --> |
|
||||||
<div |
|
||||||
:class="`${prefixCls}-form`" |
|
||||||
class="enter-x relative mx-auto my-auto w-full rounded-md px-5 py-8 shadow-md xl:ml-16 lg:w-2/4 sm:w-3/4 xl:w-auto xl:bg-transparent xl:p-4 sm:px-8 xl:shadow-none" |
|
||||||
> |
|
||||||
<SSOForm /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style lang="less"> |
|
||||||
@prefix-cls: ~'@{namespace}-login'; |
|
||||||
@logo-prefix-cls: ~'@{namespace}-app-logo'; |
|
||||||
@countdown-prefix-cls: ~'@{namespace}-countdown-input'; |
|
||||||
@dark-bg: #293146; |
|
||||||
|
|
||||||
html[data-theme='dark'] { |
|
||||||
.@{prefix-cls} { |
|
||||||
background-color: @dark-bg; |
|
||||||
|
|
||||||
&::before { |
|
||||||
background-image: url('@/assets/svg/login-bg-dark.svg'); |
|
||||||
} |
|
||||||
|
|
||||||
.ant-input, |
|
||||||
.ant-input-password { |
|
||||||
background-color: #232a3b; |
|
||||||
} |
|
||||||
|
|
||||||
.ant-btn:not(.ant-btn-link, .ant-btn-primary) { |
|
||||||
border: 1px solid #4a5569; |
|
||||||
} |
|
||||||
|
|
||||||
&-form { |
|
||||||
background: transparent !important; |
|
||||||
} |
|
||||||
|
|
||||||
.app-iconify { |
|
||||||
color: #fff; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
input.fix-auto-fill, |
|
||||||
.fix-auto-fill input { |
|
||||||
-webkit-text-fill-color: #c9d1d9 !important; |
|
||||||
box-shadow: inherit !important; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.@{prefix-cls} { |
|
||||||
min-height: 100%; |
|
||||||
overflow: hidden; |
|
||||||
|
|
||||||
@media (max-width: @screen-xl) { |
|
||||||
background-color: #293146; |
|
||||||
|
|
||||||
.@{prefix-cls}-form { |
|
||||||
background-color: #fff; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&::before { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
margin-left: -48%; |
|
||||||
content: ''; |
|
||||||
background-image: url('@/assets/svg/login-bg.svg'); |
|
||||||
background-repeat: no-repeat; |
|
||||||
background-position: 100%; |
|
||||||
background-size: auto 100%; |
|
||||||
|
|
||||||
@media (max-width: @screen-xl) { |
|
||||||
display: none; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.@{logo-prefix-cls} { |
|
||||||
position: absolute; |
|
||||||
top: 12px; |
|
||||||
height: 30px; |
|
||||||
|
|
||||||
&__title { |
|
||||||
font-size: 16px; |
|
||||||
color: #fff; |
|
||||||
} |
|
||||||
|
|
||||||
img { |
|
||||||
width: 32px; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.container { |
|
||||||
.@{logo-prefix-cls} { |
|
||||||
display: flex; |
|
||||||
width: 60%; |
|
||||||
height: 80px; |
|
||||||
|
|
||||||
&__title { |
|
||||||
font-size: 24px; |
|
||||||
color: #fff; |
|
||||||
} |
|
||||||
|
|
||||||
img { |
|
||||||
width: 48px; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-sign-in-way { |
|
||||||
.anticon { |
|
||||||
font-size: 22px; |
|
||||||
color: #888; |
|
||||||
cursor: pointer; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
input:not([type='checkbox']) { |
|
||||||
min-width: 360px; |
|
||||||
|
|
||||||
@media (max-width: @screen-xl) { |
|
||||||
min-width: 320px; |
|
||||||
} |
|
||||||
|
|
||||||
@media (max-width: @screen-lg) { |
|
||||||
min-width: 260px; |
|
||||||
} |
|
||||||
|
|
||||||
@media (max-width: @screen-md) { |
|
||||||
min-width: 240px; |
|
||||||
} |
|
||||||
|
|
||||||
@media (max-width: @screen-sm) { |
|
||||||
min-width: 160px; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.@{countdown-prefix-cls} input { |
|
||||||
min-width: unset; |
|
||||||
} |
|
||||||
|
|
||||||
.ant-divider-inner-text { |
|
||||||
font-size: 12px; |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
@ -0,0 +1,59 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { formSchema } from './data' |
||||||
|
import { useI18n } from '@/hooks/web/useI18n' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { BasicForm, useForm } from '@/components/Form' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import { createDept, updateDept } from '@/api/system/dept' |
||||||
|
import type { Department } from '@/api/system/dept/types' |
||||||
|
|
||||||
|
defineOptions({ name: 'DeptFormModal' }) |
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'register']) |
||||||
|
const { t } = useI18n() |
||||||
|
|
||||||
|
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||||
|
labelWidth: 120, |
||||||
|
baseColProps: { span: 24 }, |
||||||
|
schemas: formSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
actionColOptions: { span: 23 }, |
||||||
|
}) |
||||||
|
|
||||||
|
const isUpdate = ref(false) |
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Department) => { |
||||||
|
isUpdate.value = true |
||||||
|
setFieldsValue({ |
||||||
|
...data, |
||||||
|
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
async function handleSubmit() { |
||||||
|
try { |
||||||
|
const values = await validate<Department>() |
||||||
|
setModalProps({ confirmLoading: true }) |
||||||
|
values.parentId = values.parentId ? values.parentId : '0' |
||||||
|
await (isUpdate.value ? updateDept(values) : createDept(values)) |
||||||
|
closeModal() |
||||||
|
emit('success') |
||||||
|
useMessage().createMessage.success(t('common.saveSuccessText')) |
||||||
|
} |
||||||
|
finally { |
||||||
|
setModalProps({ confirmLoading: false }) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicModal |
||||||
|
v-bind="$attrs" |
||||||
|
:title="isUpdate ? t('action.edit') : t('action.create')" |
||||||
|
@register="registerModal" |
||||||
|
@ok="handleSubmit" |
||||||
|
@cancel="isUpdate = false" |
||||||
|
> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
@ -1,61 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { formSchema } from './dept.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createDept, getDept, updateDept } from '@/api/system/dept' |
|
||||||
|
|
||||||
defineOptions({ name: 'DeptModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const isUpdate = ref(true) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
isUpdate.value = !!data?.isUpdate |
|
||||||
if (unref(isUpdate)) { |
|
||||||
const res = await getDept(data.record.id) |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
if (unref(isUpdate)) |
|
||||||
await updateDept(values) |
|
||||||
else |
|
||||||
await createDept(values) |
|
||||||
|
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal |
|
||||||
v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" |
|
||||||
@ok="handleSubmit" |
|
||||||
> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -0,0 +1,85 @@ |
|||||||
|
import { lazyGetDeptList } from '@/api/system/dept' |
||||||
|
import { getAllTenants } from '@/api/system/tenant' |
||||||
|
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||||
|
|
||||||
|
export const columns: BasicColumn[] = [ |
||||||
|
{ |
||||||
|
title: '部门名称', |
||||||
|
dataIndex: 'deptName', |
||||||
|
width: 260, |
||||||
|
align: 'left', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '所属租户', |
||||||
|
dataIndex: 'tenantName', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const searchFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
label: '部门名称', |
||||||
|
field: 'deptName', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 6 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属租户', |
||||||
|
field: 'tenantId', |
||||||
|
component: 'ApiSelect', |
||||||
|
componentProps: { |
||||||
|
api: getAllTenants, |
||||||
|
valueField: 'tenantId', |
||||||
|
labelField: 'tenantName', |
||||||
|
}, |
||||||
|
colProps: { span: 6 }, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const formSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
field: 'id', |
||||||
|
show: false, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '上级部门', |
||||||
|
field: 'parentId', |
||||||
|
component: 'ApiTreeSelect', |
||||||
|
componentProps: { |
||||||
|
async api() { |
||||||
|
try { |
||||||
|
const res = await lazyGetDeptList() |
||||||
|
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||||
|
} |
||||||
|
catch { |
||||||
|
return [] |
||||||
|
} |
||||||
|
}, |
||||||
|
async loadData(treeNode) { |
||||||
|
try { |
||||||
|
const res = await lazyGetDeptList({ parentId: treeNode.id }) |
||||||
|
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||||
|
} |
||||||
|
catch { |
||||||
|
return [] |
||||||
|
} |
||||||
|
}, |
||||||
|
valueField: 'id', |
||||||
|
labelField: 'deptName', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '部门名称', |
||||||
|
field: 'deptName', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '显示顺序', |
||||||
|
field: 'sort', |
||||||
|
required: true, |
||||||
|
defaultValue: 0, |
||||||
|
component: 'InputNumber', |
||||||
|
}, |
||||||
|
] |
@ -1,146 +0,0 @@ |
|||||||
import { listSimpleDept } from '@/api/system/dept' |
|
||||||
import { getListSimpleUsers } from '@/api/system/user' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useRender } from '@/components/Table' |
|
||||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|
||||||
|
|
||||||
let userOptions: any[] = [] |
|
||||||
|
|
||||||
async function getUserList() { |
|
||||||
const res = await getListSimpleUsers() |
|
||||||
userOptions = res |
|
||||||
} |
|
||||||
|
|
||||||
await getUserList() |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '部门名称', |
|
||||||
dataIndex: 'name', |
|
||||||
width: 260, |
|
||||||
align: 'left', |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '负责人', |
|
||||||
dataIndex: 'leaderUserId', |
|
||||||
width: 120, |
|
||||||
customRender: ({ text }) => { |
|
||||||
if (!text) |
|
||||||
return '未设置' |
|
||||||
|
|
||||||
for (const user of userOptions) { |
|
||||||
if (text === user.id) |
|
||||||
return user.nickname |
|
||||||
} |
|
||||||
return `未知【${text}】` |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '排序', |
|
||||||
dataIndex: 'sort', |
|
||||||
width: 60, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '状态', |
|
||||||
dataIndex: 'status', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDate(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '部门名称', |
|
||||||
field: 'name', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
export const formSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '上级部门', |
|
||||||
field: 'parentId', |
|
||||||
required: true, |
|
||||||
component: 'ApiTreeSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => listSimpleDept(), |
|
||||||
parentLabel: '主类目', |
|
||||||
handleTree: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '部门名称', |
|
||||||
field: 'name', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '显示顺序', |
|
||||||
field: 'sort', |
|
||||||
required: true, |
|
||||||
defaultValue: 0, |
|
||||||
component: 'InputNumber', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '负责人', |
|
||||||
field: 'leaderUserId', |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => getListSimpleUsers(), |
|
||||||
labelField: 'nickname', |
|
||||||
valueField: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联系电话', |
|
||||||
field: 'phone', |
|
||||||
required: true, |
|
||||||
rules: [ |
|
||||||
{ |
|
||||||
pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, |
|
||||||
message: '请输入正确的手机号码', |
|
||||||
trigger: 'blur', |
|
||||||
}, |
|
||||||
], |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '邮箱', |
|
||||||
field: 'email', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '部门状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
@ -0,0 +1,55 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { formSchema } from './data' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { BasicForm, useForm } from '@/components/Form' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import { createMenu, updateMenu } from '@/api/system/menu' |
||||||
|
import type { MenuItem } from '@/api/system/menu/types' |
||||||
|
|
||||||
|
defineOptions({ name: 'SystemMenuFormModal' }) |
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'register']) |
||||||
|
|
||||||
|
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||||
|
labelWidth: 140, |
||||||
|
baseColProps: { span: 24 }, |
||||||
|
schemas: formSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const isUpdate = ref(true) |
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner((data: MenuItem) => { |
||||||
|
isUpdate.value = true |
||||||
|
setFieldsValue({ |
||||||
|
...data, |
||||||
|
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
async function handleSubmit() { |
||||||
|
try { |
||||||
|
const values = await validate<MenuItem>() |
||||||
|
setModalProps({ confirmLoading: true }) |
||||||
|
await (isUpdate.value ? updateMenu(values) : createMenu(values)) |
||||||
|
emit('success') |
||||||
|
closeModal() |
||||||
|
useMessage().createMessage.success('保存成功') |
||||||
|
} |
||||||
|
finally { |
||||||
|
setModalProps({ confirmLoading: false }) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicModal |
||||||
|
v-bind="$attrs" |
||||||
|
:title="isUpdate ? '编辑' : '新建'" |
||||||
|
@register="registerModal" |
||||||
|
@ok="handleSubmit" |
||||||
|
@cancel="isUpdate = false" |
||||||
|
> |
||||||
|
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
@ -1,58 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { formSchema } from './menu.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createMenu, getMenu, updateMenu } from '@/api/system/menu' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMenuModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const isUpdate = ref(true) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
isUpdate.value = !!data?.isUpdate |
|
||||||
if (unref(isUpdate)) { |
|
||||||
const res = await getMenu(data.record.id) |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
if (unref(isUpdate)) |
|
||||||
await updateMenu(values) |
|
||||||
else |
|
||||||
await createMenu(values) |
|
||||||
|
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -0,0 +1,56 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { formSchema } from './data' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { BasicForm, useForm } from '@/components/Form' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import { createRole, updateRole } from '@/api/system/role' |
||||||
|
import type { Role } from '@/api/system/role/types' |
||||||
|
|
||||||
|
defineOptions({ name: 'SystemRoleFormModal' }) |
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'register']) |
||||||
|
|
||||||
|
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||||
|
labelWidth: 120, |
||||||
|
baseColProps: { span: 24 }, |
||||||
|
schemas: formSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const isUpdate = ref(false) |
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Role) => { |
||||||
|
isUpdate.value = true |
||||||
|
setFieldsValue({ |
||||||
|
...data, |
||||||
|
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
async function handleSubmit() { |
||||||
|
try { |
||||||
|
const values = await validate<Role>() |
||||||
|
values.parentId = values.parentId ? values.parentId : '0' // set default value |
||||||
|
setModalProps({ confirmLoading: true }) |
||||||
|
await (isUpdate.value ? updateRole(values) : createRole(values)) |
||||||
|
closeModal() |
||||||
|
emit('success') |
||||||
|
useMessage().createMessage.success('保存成功') |
||||||
|
} |
||||||
|
finally { |
||||||
|
setModalProps({ confirmLoading: false }) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicModal |
||||||
|
v-bind="$attrs" |
||||||
|
:title="isUpdate ? '编辑' : '新增'" |
||||||
|
@register="registerModal" |
||||||
|
@ok="handleSubmit" |
||||||
|
@cancel="isUpdate = false" |
||||||
|
> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
@ -1,122 +1,54 @@ |
|||||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||||
import { ref, unref } from 'vue' |
import { ref } from 'vue' |
||||||
import { without } from 'lodash-es' |
import { useAsyncState } from '@vueuse/core' |
||||||
import { menuScopeFormSchema } from './role.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
import { getRole } from '@/api/system/role' |
|
||||||
import type { CheckKeys, CheckedEvent, TreeItem } from '@/components/Tree' |
|
||||||
import { BasicTree } from '@/components/Tree' |
import { BasicTree } from '@/components/Tree' |
||||||
import { listSimpleMenus } from '@/api/system/menu' |
import { assignMenuToRole, getMenuIdsByRole, getMenuTree } from '@/api/system/role' |
||||||
import { handleTree } from '@/utils/tree' |
import { useMessage } from '@/hooks/web/useMessage' |
||||||
import { assignRoleMenu, listRoleMenus } from '@/api/system/permission' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemRoleMenuModal' }) |
|
||||||
|
|
||||||
|
defineOptions({ name: 'RoleMenuModal' }) |
||||||
const emit = defineEmits(['success', 'register']) |
const emit = defineEmits(['success', 'register']) |
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const treeData = ref<TreeItem[]>([]) |
|
||||||
const menuKeys = ref<number[]>([]) |
|
||||||
const menuHalfKeys = ref<number[]>([]) |
|
||||||
|
|
||||||
// 默认展开的层级 |
const checkedIds = ref<string[]>([]) |
||||||
const defaultExpandLevel = ref<number>(1) |
const { state, execute } = useAsyncState(getMenuTree, [], { immediate: false }) |
||||||
// 祖先节点list |
|
||||||
const parentIdSets = ref<Set<number>>(new Set()) |
|
||||||
const treeRef = ref() |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
let roleId: string |
||||||
labelWidth: 120, |
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => { |
||||||
baseColProps: { span: 24 }, |
try { |
||||||
schemas: menuScopeFormSchema, |
if (!state.value.length) |
||||||
showActionButtonGroup: false, |
await execute() |
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
checkedIds.value = await getMenuIdsByRole(roleId = id) |
||||||
await resetFields() |
|
||||||
menuReset() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
if (unref(treeData).length === 0) { |
|
||||||
const res = await listSimpleMenus() |
|
||||||
treeData.value = handleTree(res, 'id') |
|
||||||
// 去重 拿到所有的父节点 |
|
||||||
parentIdSets.value = new Set(res.map(item => item.parentId)) |
|
||||||
} |
} |
||||||
const role = await getRole(data.record.id) |
catch {} |
||||||
const menuIds = await listRoleMenus(data.record.id) |
|
||||||
|
|
||||||
// https://www.lodashjs.com/docs/lodash.without |
|
||||||
// 默认关联节点 需要排除所有的祖先节点 否则会全部勾选 |
|
||||||
// 只保留子节点 关联情况下会自己选中父节点 |
|
||||||
// 排除祖先节点后的子节点 达到"独立"的效果 但可以进行关联选择 |
|
||||||
const excludeParentIds = without(menuIds, ...Array.from(parentIdSets.value)) |
|
||||||
// 这里是后期更新/新增需要用的 需要使用原始参数 |
|
||||||
menuKeys.value = menuIds |
|
||||||
// 这里是view需要的 需要排除祖先节点才能正常显示 否则传入父节点会勾选所有子节点 |
|
||||||
role.menuIds = excludeParentIds |
|
||||||
// 这里只负责显示 后期传递参数不使用这里 所以不用祖先节点 |
|
||||||
await setFieldsValue({ ...role }) |
|
||||||
|
|
||||||
// 默认展开的层级 |
|
||||||
if (unref(treeRef)) |
|
||||||
unref(treeRef).filterByLevel(defaultExpandLevel.value) |
|
||||||
}) |
}) |
||||||
|
|
||||||
async function handleSubmit() { |
function handleSubmit() { |
||||||
try { |
setModalProps({ confirmLoading: true }) |
||||||
const values = await validate() |
assignMenuToRole({ |
||||||
setModalProps({ confirmLoading: true }) |
roleId, |
||||||
await assignRoleMenu({ |
menuIds: checkedIds.value, |
||||||
roleId: values.id, |
}).then(() => { |
||||||
menuIds: [...menuKeys.value, ...menuHalfKeys.value], |
|
||||||
}) |
|
||||||
closeModal() |
closeModal() |
||||||
emit('success') |
emit('success') |
||||||
createMessage.success(t('common.saveSuccessText')) |
checkedIds.value = [] |
||||||
} |
useMessage().createMessage.success('操作成功') |
||||||
finally { |
}).finally(() => { |
||||||
setModalProps({ confirmLoading: false }) |
setModalProps({ confirmLoading: false }) |
||||||
} |
}) |
||||||
} |
|
||||||
|
|
||||||
function menuReset() { |
|
||||||
menuKeys.value = [] |
|
||||||
menuHalfKeys.value = [] |
|
||||||
} |
|
||||||
|
|
||||||
function menuCheck(checkedKeys: CheckKeys, event: CheckedEvent) { |
|
||||||
if (Array.isArray(checkedKeys)) { |
|
||||||
// 这里是子节点的ID |
|
||||||
menuKeys.value = checkedKeys as number[] |
|
||||||
// 这里是父节点的ID 默认空数组 |
|
||||||
menuHalfKeys.value = (event.halfCheckedKeys as number[]) || [] |
|
||||||
} |
|
||||||
} |
} |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<BasicModal v-bind="$attrs" title="修改角色菜单权限" @register="registerModal" @ok="handleSubmit"> |
<BasicModal v-bind="$attrs" title="菜单权限配置" width="20%" @register="registerModal" @ok="handleSubmit"> |
||||||
<BasicForm @register="registerForm"> |
<BasicTree |
||||||
<template #menuIds="{ model, field }"> |
v-if="state.length" |
||||||
<BasicTree |
v-model:value="checkedIds" |
||||||
v-if="treeData.length" |
checkable |
||||||
ref="treeRef" |
default-expand-all |
||||||
v-model:checkedKeys="model[field]" |
:tree-data="state" |
||||||
:tree-data="treeData" |
:selectable="false" |
||||||
:field-names="{ title: 'name', key: 'id' }" |
:field-names="{ key: 'id' }" |
||||||
toolbar |
/> |
||||||
checkable |
|
||||||
search |
|
||||||
:show-strictly-button="false" |
|
||||||
:selectable="false" |
|
||||||
title="菜单分配" |
|
||||||
@check="menuCheck" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</BasicForm> |
|
||||||
</BasicModal> |
</BasicModal> |
||||||
</template> |
</template> |
||||||
|
@ -1,58 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { formSchema } from './role.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createRole, getRole, updateRole } from '@/api/system/role' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemRoleModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const isUpdate = ref(true) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
isUpdate.value = !!data?.isUpdate |
|
||||||
if (unref(isUpdate)) { |
|
||||||
const res = await getRole(data.record.id) |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
if (unref(isUpdate)) |
|
||||||
await updateRole(values) |
|
||||||
else |
|
||||||
await createRole(values) |
|
||||||
|
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,73 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { dataScopeFormSchema } from './role.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import type { TreeItem } from '@/components/Tree' |
|
||||||
import { BasicTree } from '@/components/Tree' |
|
||||||
import { getRole } from '@/api/system/role' |
|
||||||
import { listSimpleDept } from '@/api/system/dept' |
|
||||||
import { handleTree } from '@/utils/tree' |
|
||||||
import { assignRoleDataScope } from '@/api/system/permission' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemRoleScopeModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const treeData = ref<TreeItem[]>([]) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: dataScopeFormSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
if (unref(treeData).length === 0) { |
|
||||||
const res = await listSimpleDept() |
|
||||||
treeData.value = handleTree(res, 'id') |
|
||||||
} |
|
||||||
const res = await getRole(data.record.id) |
|
||||||
res.roleId = data.record.id |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await assignRoleDataScope(values) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" title="编辑角色数据权限" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm"> |
|
||||||
<template #dataScopeDeptIds="{ model, field }"> |
|
||||||
<BasicTree |
|
||||||
v-model:value="model[field]" |
|
||||||
:tree-data="treeData" |
|
||||||
:field-names="{ title: 'name', key: 'id' }" |
|
||||||
:check-strictly="false" |
|
||||||
checkable |
|
||||||
toolbar |
|
||||||
title="部门分配" |
|
||||||
/> |
|
||||||
</template> |
|
||||||
</BasicForm> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -0,0 +1,116 @@ |
|||||||
|
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||||
|
import { getAllTenants } from '@/api/system/tenant' |
||||||
|
import { getRoleTree } from '@/api/system/role' |
||||||
|
import { getTenantId } from '@/utils/auth' |
||||||
|
|
||||||
|
export const columns: BasicColumn[] = [ |
||||||
|
{ |
||||||
|
title: '角色名称', |
||||||
|
dataIndex: 'roleName', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '所属租户', |
||||||
|
dataIndex: 'tenantName', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '角色别名', |
||||||
|
dataIndex: 'roleAlias', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '角色排序', |
||||||
|
dataIndex: 'sort', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const searchFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
label: '角色名称', |
||||||
|
field: 'roleName', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 7 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属租户', |
||||||
|
field: 'tenantId', |
||||||
|
component: 'ApiSelect', |
||||||
|
componentProps: { |
||||||
|
api: getAllTenants, |
||||||
|
valueField: 'tenantId', |
||||||
|
labelField: 'tenantName', |
||||||
|
}, |
||||||
|
colProps: { span: 7 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色别名', |
||||||
|
field: 'roleAlias', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 7 }, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const formSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
field: 'id', |
||||||
|
show: false, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色名称', |
||||||
|
field: 'roleName', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色别名', |
||||||
|
field: 'roleAlias', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色排序', |
||||||
|
field: 'sort', |
||||||
|
required: true, |
||||||
|
defaultValue: 0, |
||||||
|
component: 'InputNumber', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '上级角色', |
||||||
|
field: 'parentId', |
||||||
|
component: 'ApiTreeSelect', |
||||||
|
componentProps: { |
||||||
|
api: () => getRoleTree({ tenantId: getTenantId() }), |
||||||
|
valueField: 'id', |
||||||
|
labelField: 'title', |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const menuScopeFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
label: '角色编号', |
||||||
|
field: 'id', |
||||||
|
show: false, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色名称', |
||||||
|
field: 'name', |
||||||
|
dynamicDisabled: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '角色标识', |
||||||
|
field: 'code', |
||||||
|
dynamicDisabled: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '菜单权限', |
||||||
|
field: 'menuIds', |
||||||
|
slot: 'menuIds', |
||||||
|
}, |
||||||
|
] |
@ -1,184 +0,0 @@ |
|||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useRender } from '@/components/Table' |
|
||||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|
||||||
import { SystemDataScopeEnum } from '@/enums/systemEnum' |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '角色编号', |
|
||||||
dataIndex: 'id', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '角色名称', |
|
||||||
dataIndex: 'name', |
|
||||||
width: 150, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '角色标识', |
|
||||||
dataIndex: 'code', |
|
||||||
width: 150, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '角色类型', |
|
||||||
dataIndex: 'type', |
|
||||||
width: 150, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDict(text, DICT_TYPE.SYSTEM_ROLE_TYPE) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '显示顺序', |
|
||||||
dataIndex: 'sort', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '状态', |
|
||||||
dataIndex: 'status', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDate(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '角色名称', |
|
||||||
field: 'name', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色标识', |
|
||||||
field: 'code', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '创建时间', |
|
||||||
field: 'createTime', |
|
||||||
component: 'RangePicker', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const formSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色名称', |
|
||||||
field: 'name', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色标识', |
|
||||||
field: 'code', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色顺序', |
|
||||||
field: 'sort', |
|
||||||
required: true, |
|
||||||
defaultValue: 0, |
|
||||||
component: 'InputNumber', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '备注', |
|
||||||
field: 'remark', |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const menuScopeFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '角色编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色名称', |
|
||||||
field: 'name', |
|
||||||
dynamicDisabled: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色标识', |
|
||||||
field: 'code', |
|
||||||
dynamicDisabled: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '菜单权限', |
|
||||||
field: 'menuIds', |
|
||||||
slot: 'menuIds', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const dataScopeFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '角色编号', |
|
||||||
field: 'roleId', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色名称', |
|
||||||
field: 'name', |
|
||||||
dynamicDisabled: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色标识', |
|
||||||
field: 'code', |
|
||||||
dynamicDisabled: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '权限范围', |
|
||||||
field: 'dataScope', |
|
||||||
required: true, |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE), |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '数据权限', |
|
||||||
field: 'dataScopeDeptIds', |
|
||||||
ifShow: ({ values }) => values.dataScope === SystemDataScopeEnum.DEPT_CUSTOM, |
|
||||||
slot: 'dataScopeDeptIds', |
|
||||||
}, |
|
||||||
] |
|
@ -0,0 +1,52 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { formSchema } from './data' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { BasicForm, useForm } from '@/components/Form' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import { createTenant, updateTenant } from '@/api/system/tenant' |
||||||
|
import type { Tenant } from '@/api/system/tenant/types' |
||||||
|
|
||||||
|
defineOptions({ name: 'TenantFormModal' }) |
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'register']) |
||||||
|
|
||||||
|
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||||
|
labelWidth: 120, |
||||||
|
baseColProps: { span: 24 }, |
||||||
|
schemas: formSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const isUpdate = ref(false) |
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||||
|
isUpdate.value = true |
||||||
|
setFieldsValue(data) |
||||||
|
}) |
||||||
|
|
||||||
|
async function handleSubmit() { |
||||||
|
try { |
||||||
|
const values = await validate<Tenant>() |
||||||
|
setModalProps({ confirmLoading: true }) |
||||||
|
await (isUpdate.value ? updateTenant(values) : createTenant(values)) |
||||||
|
closeModal() |
||||||
|
emit('success') |
||||||
|
useMessage().createMessage.success('保存成功') |
||||||
|
} |
||||||
|
finally { |
||||||
|
setModalProps({ confirmLoading: false }) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicModal |
||||||
|
v-bind="$attrs" |
||||||
|
:title="isUpdate ? '编辑' : '新增'" |
||||||
|
@register="registerModal" |
||||||
|
@ok="handleSubmit" |
||||||
|
@cancel="isUpdate = false" |
||||||
|
> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
@ -1,58 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { formSchema } from './tenant.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createTenant, getTenant, updateTenant } from '@/api/system/tenant' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemTenantModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const isUpdate = ref(true) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
isUpdate.value = !!data?.isUpdate |
|
||||||
if (unref(isUpdate)) { |
|
||||||
const res = await getTenant(data.record.id) |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
if (unref(isUpdate)) |
|
||||||
await updateTenant(values) |
|
||||||
else |
|
||||||
await createTenant(values) |
|
||||||
|
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -0,0 +1,106 @@ |
|||||||
|
import { h } from 'vue' |
||||||
|
import { Tag } from 'ant-design-vue' |
||||||
|
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||||
|
|
||||||
|
export const columns: BasicColumn[] = [ |
||||||
|
{ |
||||||
|
title: '租户 ID', |
||||||
|
dataIndex: 'tenantId', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '租户名称', |
||||||
|
dataIndex: 'tenantName', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '登录账号', |
||||||
|
dataIndex: 'adminAccount', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '联系人名称', |
||||||
|
dataIndex: 'contactName', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '联系人手机', |
||||||
|
dataIndex: 'contactMobile', |
||||||
|
width: 150, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '过期时间', |
||||||
|
dataIndex: 'expireTime', |
||||||
|
width: 150, |
||||||
|
customRender({ value }) { |
||||||
|
return h(Tag, { color: value ? 'default' : 'blue' }, () => value || '无限制') |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const searchFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
label: '租户 ID', |
||||||
|
field: 'tenantId', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 5 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '租户名称', |
||||||
|
field: 'tenantName', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 5 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '联系人名称', |
||||||
|
field: 'contactName', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 5 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '联系人手机', |
||||||
|
field: 'contactMobile', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 5 }, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const formSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
field: 'id', |
||||||
|
show: false, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '租户名', |
||||||
|
field: 'tenantName', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '登录账号', |
||||||
|
field: 'adminAccount', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
rules: [ |
||||||
|
{ min: 6, max: 30, message: '登陆账号长度为6-30' }, |
||||||
|
], |
||||||
|
// cannot edit
|
||||||
|
show: ({ values }) => !values.id, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '联系人名称', |
||||||
|
field: 'contactName', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '联系人手机', |
||||||
|
field: 'contactMobile', |
||||||
|
component: 'InputNumber', |
||||||
|
componentProps: { |
||||||
|
controls: false, |
||||||
|
precision: 0, |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
@ -1,191 +0,0 @@ |
|||||||
import { getTenantPackageList } from '@/api/system/tenantPackage' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useRender } from '@/components/Table' |
|
||||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '租户编号', |
|
||||||
dataIndex: 'id', |
|
||||||
width: 100, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '租户名', |
|
||||||
dataIndex: 'name', |
|
||||||
width: 180, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '租户套餐', |
|
||||||
dataIndex: 'packageId', |
|
||||||
width: 100, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '联系人', |
|
||||||
dataIndex: 'contactName', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '联系手机', |
|
||||||
dataIndex: 'contactMobile', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '账号额度', |
|
||||||
dataIndex: 'accountCount', |
|
||||||
width: 120, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderTag(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '过期时间', |
|
||||||
dataIndex: 'expireTime', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDate(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '绑定域名', |
|
||||||
dataIndex: 'website', |
|
||||||
width: 200, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '租户状态', |
|
||||||
dataIndex: 'status', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '备注', |
|
||||||
dataIndex: 'remark', |
|
||||||
width: 180, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDate(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '租户名', |
|
||||||
field: 'name', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联系人', |
|
||||||
field: 'contactName', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联系手机', |
|
||||||
field: 'contactMobile', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '创建时间', |
|
||||||
field: 'createTime', |
|
||||||
component: 'RangePicker', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const formSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '租户名', |
|
||||||
field: 'name', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '租户套餐', |
|
||||||
field: 'packageId', |
|
||||||
required: true, |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => getTenantPackageList(), |
|
||||||
labelField: 'name', |
|
||||||
valueField: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联系人', |
|
||||||
field: 'contactName', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '联系手机', |
|
||||||
field: 'contactMobile', |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户名称', |
|
||||||
field: 'username', |
|
||||||
component: 'Input', |
|
||||||
ifShow: ({ values }) => !values.id, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户密码', |
|
||||||
field: 'password', |
|
||||||
component: 'InputPassword', |
|
||||||
ifShow: ({ values }) => !values.id, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '账号额度', |
|
||||||
field: 'accountCount', |
|
||||||
required: true, |
|
||||||
defaultValue: 0, |
|
||||||
component: 'InputNumber', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '过期时间', |
|
||||||
field: 'expireTime', |
|
||||||
required: true, |
|
||||||
component: 'DatePicker', |
|
||||||
componentProps: { |
|
||||||
showTime: true, |
|
||||||
format: 'YYYY-MM-DD HH:mm:ss', |
|
||||||
valueFormat: 'x', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '绑定域名', |
|
||||||
field: 'website', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '租户状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
@ -1,43 +1,63 @@ |
|||||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||||
import { onMounted, ref } from 'vue' |
import { onMounted, ref } from 'vue' |
||||||
|
import type { EventDataNode } from 'ant-design-vue/es/tree' |
||||||
import type { TreeItem } from '@/components/Tree' |
|
||||||
import { BasicTree } from '@/components/Tree' |
import { BasicTree } from '@/components/Tree' |
||||||
import { listSimpleDept } from '@/api/system/dept' |
import { lazyGetDeptList } from '@/api/system/dept' |
||||||
import { handleTree } from '@/utils/tree' |
import type { Department, LazyGetDeptListParams } from '@/api/system/dept/types' |
||||||
|
|
||||||
defineOptions({ name: 'SystemDeptTree' }) |
defineOptions({ name: 'SystemDeptTree' }) |
||||||
|
defineProps<{ dept?: string }>() |
||||||
|
const emit = defineEmits(['update:dept']) |
||||||
|
|
||||||
const emit = defineEmits(['select']) |
const departmentList = ref<Department[]>([]) |
||||||
const treeRef = ref() |
|
||||||
const treeData = ref<TreeItem[]>([]) |
|
||||||
|
|
||||||
async function fetch() { |
async function requestDeptList(params?: LazyGetDeptListParams) { |
||||||
const res = await listSimpleDept() |
return lazyGetDeptList(params) |
||||||
treeData.value = handleTree(res, 'id') |
.then((res) => { |
||||||
|
return res.map((item) => { |
||||||
|
return { |
||||||
|
...item, |
||||||
|
isLeaf: !item.hasChildren, |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
function handleSelect(keys) { |
const basicTreeRef = ref<InstanceType<typeof BasicTree>>() |
||||||
emit('select', keys[0]) |
async function onLoadDeptList(treeNode: EventDataNode) { |
||||||
|
try { |
||||||
|
return await requestDeptList({ parentId: treeNode.id }) |
||||||
|
} |
||||||
|
catch { |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
onMounted(() => { |
onMounted(() => { |
||||||
fetch() |
requestDeptList() |
||||||
|
.then((res) => { |
||||||
|
departmentList.value = res |
||||||
|
}) |
||||||
}) |
}) |
||||||
|
|
||||||
|
function onSelect(value: string[]) { |
||||||
|
emit('update:dept', value[0] || '') |
||||||
|
} |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<div class="m-4 mr-0 overflow-hidden" v-bind="$attrs"> |
<div class="box-border h-full py-12px pl-12px" v-bind="$attrs"> |
||||||
<BasicTree |
<BasicTree |
||||||
ref="treeRef" |
ref="basicTreeRef" |
||||||
title="部门列表" |
title="部门列表" |
||||||
toolbar |
|
||||||
search |
|
||||||
tree-wrapper-class-name="h-[calc(100%-35px)] overflow-auto" |
|
||||||
:click-row-to-expand="false" |
:click-row-to-expand="false" |
||||||
:tree-data="treeData" |
:tree-data="departmentList" |
||||||
:field-names="{ key: 'id', title: 'name' }" |
:field-names="{ key: 'id', title: 'deptName' }" |
||||||
@select="handleSelect" |
:load-data="onLoadDeptList" |
||||||
/> |
@select="onSelect" |
||||||
|
> |
||||||
|
<template #icon> |
||||||
|
<span class="i-ant-design:deployment-unit-outlined" /> |
||||||
|
</template> |
||||||
|
</BasicTree> |
||||||
</div> |
</div> |
||||||
</template> |
</template> |
||||||
|
@ -1,58 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { ref, unref } from 'vue' |
|
||||||
import { formSchema } from './user.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { createUser, getUser, updateUser } from '@/api/system/user' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemUserModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const isUpdate = ref(true) |
|
||||||
|
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: formSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
isUpdate.value = !!data?.isUpdate |
|
||||||
if (unref(isUpdate)) { |
|
||||||
const res = await getUser(data.record.id) |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() as any |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
if (unref(isUpdate)) |
|
||||||
await updateUser(values) |
|
||||||
else |
|
||||||
await createUser(values) |
|
||||||
|
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -1,52 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { userRoleFormSchema } from './user.data' |
|
||||||
import { useI18n } from '@/hooks/web/useI18n' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { BasicForm, useForm } from '@/components/Form' |
|
||||||
import { BasicModal, useModalInner } from '@/components/Modal' |
|
||||||
import { getUser } from '@/api/system/user' |
|
||||||
import { assignUserRole, listUserRoles } from '@/api/system/permission' |
|
||||||
|
|
||||||
defineOptions({ name: 'SystemUserRoleModal' }) |
|
||||||
|
|
||||||
const emit = defineEmits(['success', 'register']) |
|
||||||
const { t } = useI18n() |
|
||||||
const { createMessage } = useMessage() |
|
||||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
|
||||||
labelWidth: 120, |
|
||||||
baseColProps: { span: 24 }, |
|
||||||
schemas: userRoleFormSchema, |
|
||||||
showActionButtonGroup: false, |
|
||||||
actionColOptions: { span: 23 }, |
|
||||||
}) |
|
||||||
|
|
||||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
|
||||||
resetFields() |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
|
|
||||||
const res = await getUser(data.record.id) |
|
||||||
const roleIds = await listUserRoles(data.record.id) |
|
||||||
res.roleIds = roleIds |
|
||||||
setFieldsValue({ ...res }) |
|
||||||
}) |
|
||||||
|
|
||||||
async function handleSubmit() { |
|
||||||
try { |
|
||||||
const values = await validate() |
|
||||||
setModalProps({ confirmLoading: true }) |
|
||||||
await assignUserRole({ userId: values.id, roleIds: values.roleIds }) |
|
||||||
closeModal() |
|
||||||
emit('success') |
|
||||||
createMessage.success(t('common.saveSuccessText')) |
|
||||||
} |
|
||||||
finally { |
|
||||||
setModalProps({ confirmLoading: false }) |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<BasicModal v-bind="$attrs" title="用户角色权限" @register="registerModal" @ok="handleSubmit"> |
|
||||||
<BasicForm @register="registerForm" /> |
|
||||||
</BasicModal> |
|
||||||
</template> |
|
@ -0,0 +1,309 @@ |
|||||||
|
import type { Ref } from 'vue' |
||||||
|
import { h } from 'vue' |
||||||
|
import { Avatar } from 'ant-design-vue' |
||||||
|
import { getRoleTree } from '@/api/system/role' |
||||||
|
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||||
|
import { getAllTenants } from '@/api/system/tenant' |
||||||
|
import { getDeptTree, lazyGetDeptList } from '@/api/system/dept' |
||||||
|
|
||||||
|
export const columns: BasicColumn[] = [ |
||||||
|
{ |
||||||
|
title: '登陆账号', |
||||||
|
dataIndex: 'account', |
||||||
|
width: 180, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '所属租户', |
||||||
|
dataIndex: 'tenantName', |
||||||
|
width: 100, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '用户姓名', |
||||||
|
dataIndex: 'realName', |
||||||
|
width: 120, |
||||||
|
customRender({ record, value }) { |
||||||
|
return h('div', [ |
||||||
|
h( |
||||||
|
Avatar, |
||||||
|
{ |
||||||
|
src: /^https?/.test(record.avatar) ? record.avatar : undefined, |
||||||
|
size: 'small', |
||||||
|
style: { marginRight: '10px' }, |
||||||
|
}, |
||||||
|
() => value?.slice(0, 1), |
||||||
|
), |
||||||
|
value, |
||||||
|
]) |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '手机号码', |
||||||
|
dataIndex: 'mobile', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '用户邮箱', |
||||||
|
dataIndex: 'email', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '所属角色', |
||||||
|
dataIndex: 'roleName', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '所属部门', |
||||||
|
dataIndex: 'deptName', |
||||||
|
width: 120, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export const searchFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
label: '账号', |
||||||
|
field: 'account', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 6 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '手机号码', |
||||||
|
field: 'mobile', |
||||||
|
component: 'InputNumber', |
||||||
|
componentProps: { |
||||||
|
controls: false, |
||||||
|
precision: 0, |
||||||
|
}, |
||||||
|
colProps: { span: 6 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属部门', |
||||||
|
field: 'tenantId', |
||||||
|
component: 'ApiTreeSelect', |
||||||
|
componentProps: { |
||||||
|
async api() { |
||||||
|
try { |
||||||
|
const res = await lazyGetDeptList() |
||||||
|
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||||
|
} |
||||||
|
catch { |
||||||
|
return [] |
||||||
|
} |
||||||
|
}, |
||||||
|
async loadData(treeNode) { |
||||||
|
try { |
||||||
|
const res = await lazyGetDeptList({ parentId: treeNode.id }) |
||||||
|
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||||
|
} |
||||||
|
catch { |
||||||
|
return [] |
||||||
|
} |
||||||
|
}, |
||||||
|
valueField: 'id', |
||||||
|
labelField: 'deptName', |
||||||
|
}, |
||||||
|
colProps: { span: 6 }, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] { |
||||||
|
return [ |
||||||
|
{ |
||||||
|
field: 'id', |
||||||
|
show: false, |
||||||
|
component: 'Input', |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'BaseInfo', |
||||||
|
label: '基本信息', |
||||||
|
component: 'Divider', |
||||||
|
componentProps: { |
||||||
|
plain: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '账号', |
||||||
|
field: 'account', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
ifShow: () => !isUpdate.value, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '密码', |
||||||
|
field: 'password', |
||||||
|
required: true, |
||||||
|
component: 'InputPassword', |
||||||
|
ifShow: () => !isUpdate.value, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '确认密码', |
||||||
|
field: 'confirmPassword', |
||||||
|
required: true, |
||||||
|
component: 'InputPassword', |
||||||
|
ifShow: () => !isUpdate.value, |
||||||
|
dynamicRules: ({ values }) => { |
||||||
|
return [ |
||||||
|
{ |
||||||
|
required: true, |
||||||
|
validator: (_, value) => { |
||||||
|
if (value !== values.password) |
||||||
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
|
return Promise.reject('两次输入的密码不一致!') |
||||||
|
|
||||||
|
return Promise.resolve() |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属租户', |
||||||
|
field: 'tenantId', |
||||||
|
required: true, |
||||||
|
component: 'ApiSelect', |
||||||
|
defaultValue: undefined, |
||||||
|
dynamicDisabled: () => isUpdate.value, |
||||||
|
componentProps({ formActionType, formModel }) { |
||||||
|
return { |
||||||
|
api: getAllTenants, |
||||||
|
valueField: 'tenantId', |
||||||
|
labelField: 'tenantName', |
||||||
|
onChange(value?: string) { |
||||||
|
function updateRole(treeData: { id: string, title: string }[]) { |
||||||
|
if (!isUpdate) |
||||||
|
formModel.roleId && formActionType.setFieldsValue({ roleId: undefined }) |
||||||
|
|
||||||
|
formActionType.updateSchema({ |
||||||
|
field: 'roleId', |
||||||
|
componentProps: { |
||||||
|
treeData, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
function updateDept(treeData: { id: string, title: string }[]) { |
||||||
|
if (!isUpdate) |
||||||
|
formModel.deptId && formActionType.setFieldsValue({ deptId: undefined }) |
||||||
|
|
||||||
|
formActionType.updateSchema({ |
||||||
|
field: 'deptId', |
||||||
|
componentProps: { |
||||||
|
treeData, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
if (!value) { |
||||||
|
updateRole([]) |
||||||
|
updateDept([]) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
getRoleTree({ tenantId: value }) |
||||||
|
.then((res) => { |
||||||
|
updateRole(res) |
||||||
|
}) |
||||||
|
|
||||||
|
getDeptTree({ tenantId: value }) |
||||||
|
.then((res) => { |
||||||
|
updateDept(res) |
||||||
|
}) |
||||||
|
}, |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属部门', |
||||||
|
field: 'deptId', |
||||||
|
required: true, |
||||||
|
component: 'TreeSelect', |
||||||
|
componentProps: { |
||||||
|
treeData: [], |
||||||
|
fieldNames: { |
||||||
|
label: 'title', |
||||||
|
value: 'id', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '所属角色', |
||||||
|
field: 'roleId', |
||||||
|
required: true, |
||||||
|
component: 'TreeSelect', |
||||||
|
componentProps: { |
||||||
|
treeData: [], |
||||||
|
fieldNames: { |
||||||
|
label: 'title', |
||||||
|
value: 'id', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'UserInfo', |
||||||
|
label: '用户信息', |
||||||
|
component: 'Divider', |
||||||
|
componentProps: { |
||||||
|
plain: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '用户姓名', |
||||||
|
field: 'realName', |
||||||
|
component: 'Input', |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '用户头像', |
||||||
|
field: 'avatar', |
||||||
|
component: 'FileUpload', |
||||||
|
componentProps: { |
||||||
|
maxCount: 1, |
||||||
|
fileType: 'image', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '用户邮箱', |
||||||
|
field: 'email', |
||||||
|
component: 'Input', |
||||||
|
rules: [ |
||||||
|
{ type: 'email', message: '邮箱格式不正确' }, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '手机号码', |
||||||
|
field: 'mobile', |
||||||
|
component: 'InputNumber', |
||||||
|
componentProps: { |
||||||
|
controls: false, |
||||||
|
precision: 0, |
||||||
|
}, |
||||||
|
rules: [ |
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确' }, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '用户性别', |
||||||
|
field: 'sex', |
||||||
|
component: 'Select', |
||||||
|
componentProps: { |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
label: '男', |
||||||
|
value: 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '女', |
||||||
|
value: 1, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '未知', |
||||||
|
value: 2, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '备注', |
||||||
|
field: 'remark', |
||||||
|
component: 'InputTextArea', |
||||||
|
}, |
||||||
|
] |
||||||
|
} |
@ -1,283 +0,0 @@ |
|||||||
/* eslint-disable prefer-promise-reject-errors */ |
|
||||||
import { h } from 'vue' |
|
||||||
import { Switch } from 'ant-design-vue' |
|
||||||
import dayjs from 'dayjs' |
|
||||||
import { useMessage } from '@/hooks/web/useMessage' |
|
||||||
import { listSimpleDept } from '@/api/system/dept' |
|
||||||
import { listSimplePosts } from '@/api/system/post' |
|
||||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
|
||||||
import { useRender } from '@/components/Table' |
|
||||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|
||||||
import { updateUserStatus } from '@/api/system/user' |
|
||||||
import { listSimpleRoles } from '@/api/system/role' |
|
||||||
|
|
||||||
export const columns: BasicColumn[] = [ |
|
||||||
{ |
|
||||||
title: '用户编号', |
|
||||||
dataIndex: 'id', |
|
||||||
width: 100, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '用户名称', |
|
||||||
dataIndex: 'username', |
|
||||||
width: 180, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '用户昵称', |
|
||||||
dataIndex: 'nickname', |
|
||||||
width: 100, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '部门', |
|
||||||
dataIndex: 'deptId', |
|
||||||
width: 120, |
|
||||||
customRender: ({ record }) => { |
|
||||||
return useRender.renderTag(record.dept && record.dept.name) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '手机号码', |
|
||||||
dataIndex: 'mobile', |
|
||||||
width: 120, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '状态', |
|
||||||
dataIndex: 'status', |
|
||||||
width: 180, |
|
||||||
// customRender: ({ text }) => {
|
|
||||||
// return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
|
|
||||||
// }
|
|
||||||
customRender: ({ record }) => { |
|
||||||
if (!Reflect.has(record, 'pendingStatus')) |
|
||||||
record.pendingStatus = false |
|
||||||
|
|
||||||
return h(Switch, { |
|
||||||
checked: record.status === 0, |
|
||||||
checkedChildren: '已启用', |
|
||||||
unCheckedChildren: '已禁用', |
|
||||||
loading: record.pendingStatus, |
|
||||||
onChange(checked: boolean) { |
|
||||||
record.pendingStatus = true |
|
||||||
const newStatus = checked ? 0 : 1 |
|
||||||
const { createMessage } = useMessage() |
|
||||||
updateUserStatus(record.id, newStatus) |
|
||||||
.then(() => { |
|
||||||
record.status = newStatus |
|
||||||
createMessage.success('已成功修改用户状态') |
|
||||||
}) |
|
||||||
.catch(() => { |
|
||||||
createMessage.error('修改用户状态失败') |
|
||||||
}) |
|
||||||
.finally(() => { |
|
||||||
record.pendingStatus = false |
|
||||||
}) |
|
||||||
}, |
|
||||||
}) |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
title: '创建时间', |
|
||||||
dataIndex: 'createTime', |
|
||||||
width: 180, |
|
||||||
customRender: ({ text }) => { |
|
||||||
return useRender.renderDate(text) |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const searchFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '用户名称', |
|
||||||
field: 'username', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '手机号码', |
|
||||||
field: 'mobile', |
|
||||||
component: 'Input', |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '创建时间', |
|
||||||
field: 'createTime', |
|
||||||
component: 'RangePicker', |
|
||||||
componentProps: { |
|
||||||
showTime: { |
|
||||||
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')], |
|
||||||
}, |
|
||||||
}, |
|
||||||
colProps: { span: 8 }, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const formSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户昵称', |
|
||||||
field: 'nickname', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户头像', |
|
||||||
field: 'avatar', |
|
||||||
component: 'FileUpload', |
|
||||||
componentProps: { |
|
||||||
maxCount: 1, |
|
||||||
fileType: 'image', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '归属部门', |
|
||||||
field: 'deptId', |
|
||||||
required: true, |
|
||||||
component: 'ApiTreeSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => listSimpleDept(), |
|
||||||
handleTree: 'id', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '手机号码', |
|
||||||
field: 'mobile', |
|
||||||
required: true, |
|
||||||
defaultValue: 0, |
|
||||||
component: 'InputNumber', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '邮箱', |
|
||||||
field: 'email', |
|
||||||
required: true, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户名称', |
|
||||||
field: 'username', |
|
||||||
component: 'Input', |
|
||||||
dynamicDisabled: ({ values }) => !!values.id, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户密码', |
|
||||||
field: 'password', |
|
||||||
component: 'InputPassword', |
|
||||||
ifShow: ({ values }) => !values.id, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户性别', |
|
||||||
field: 'sex', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX), |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '岗位', |
|
||||||
field: 'postIds', |
|
||||||
component: 'ApiSelect', |
|
||||||
defaultValue: [], |
|
||||||
componentProps: { |
|
||||||
api: () => listSimplePosts(), |
|
||||||
labelField: 'name', |
|
||||||
valueField: 'id', |
|
||||||
mode: 'tags', |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '状态', |
|
||||||
field: 'status', |
|
||||||
component: 'Select', |
|
||||||
componentProps: { |
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '备注', |
|
||||||
field: 'remark', |
|
||||||
component: 'InputTextArea', |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const userRoleFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
label: '编号', |
|
||||||
field: 'id', |
|
||||||
show: false, |
|
||||||
component: 'Input', |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户名称', |
|
||||||
field: 'username', |
|
||||||
component: 'Input', |
|
||||||
dynamicDisabled: () => true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '用户昵称', |
|
||||||
field: 'nickname', |
|
||||||
component: 'Input', |
|
||||||
dynamicDisabled: () => true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: '角色', |
|
||||||
field: 'roleIds', |
|
||||||
component: 'ApiSelect', |
|
||||||
componentProps: { |
|
||||||
api: () => listSimpleRoles(), |
|
||||||
labelField: 'name', |
|
||||||
valueField: 'id', |
|
||||||
mode: 'tags', |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
|
|
||||||
export const userPwdFormSchema: FormSchema[] = [ |
|
||||||
{ |
|
||||||
field: 'newPassword', |
|
||||||
label: '新密码', |
|
||||||
component: 'StrengthMeter', |
|
||||||
componentProps: { |
|
||||||
placeholder: '新密码', |
|
||||||
}, |
|
||||||
rules: [ |
|
||||||
{ |
|
||||||
required: true, |
|
||||||
message: '请输入新密码', |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
field: 'confirmPassword', |
|
||||||
label: '确认密码', |
|
||||||
component: 'InputPassword', |
|
||||||
dynamicRules: ({ values }) => { |
|
||||||
return [ |
|
||||||
{ |
|
||||||
required: true, |
|
||||||
validator: (_, value) => { |
|
||||||
if (!value) |
|
||||||
return Promise.reject('密码不能为空') |
|
||||||
|
|
||||||
if (value !== values.newPassword) |
|
||||||
return Promise.reject('两次输入的密码不一致!') |
|
||||||
|
|
||||||
return Promise.resolve() |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
||||||
}, |
|
||||||
}, |
|
||||||
] |
|
Reference in new issue