diff --git a/src/ant-design.d.ts b/src/ant-design.d.ts index cc5ddae..3de2a01 100644 --- a/src/ant-design.d.ts +++ b/src/ant-design.d.ts @@ -17,5 +17,9 @@ declare global { type _SorterResult = SorterResult type _SortOrder = SortOrder type _CheckboxChangeEvent = CheckboxChangeEvent - type _DataNode = DataNode + interface _DataNode extends DataNode { + value: React.Key + fullTitle: string + children?: _DataNode[] + } } diff --git a/src/assets/svg/refresh.svg b/src/assets/svg/refresh.svg new file mode 100644 index 0000000..5f15aae --- /dev/null +++ b/src/assets/svg/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/constants/common.constants.ts b/src/constants/common.constants.ts index fef7b4d..9d8d803 100644 --- a/src/constants/common.constants.ts +++ b/src/constants/common.constants.ts @@ -50,3 +50,5 @@ export const DATABASE_UPDATE_SUCCESS = 20020 export const DATABASE_UPDATE_FILED = 20025 export const DATABASE_DELETE_SUCCESS = 20030 export const DATABASE_DELETE_FILED = 20035 +export const DATABASE_EXECUTE_ERROR = 20040 +export const DATABASE_DUPLICATE_KEY = 20045 diff --git a/src/constants/urls.constants.ts b/src/constants/urls.constants.ts index f0aa8fa..0283a42 100644 --- a/src/constants/urls.constants.ts +++ b/src/constants/urls.constants.ts @@ -2,6 +2,7 @@ export const URL_API_LOGIN = '/login' export const URL_API_TOKEN = '/token' export const URL_API_LOGOUT = '/logout' export const URL_API_SYS_LOG = '/system/log' -export const URL_API_USER_INFO = '/system/user/info' -export const URL_API_USER_LIST = '/system/user' +export const URL_API_SYS_USER_INFO = '/system/user/info' +export const URL_API_SYS_USER = '/system/user' export const URL_API_SYS_ROLE = '/system/role' +export const URL_API_SYS_POWER = '/system/power' diff --git a/src/global.d.ts b/src/global.d.ts index ee1c048..99a99cb 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -169,7 +169,7 @@ interface TableParam { filters?: Record } -interface GetSysLogParam extends PageParam { +interface SysLogGetParam extends PageParam { searchRequestUrl?: string searchRegex?: boolean searchStartTime?: string @@ -195,7 +195,7 @@ interface SysLogGetVo { operateUsername: string } -interface GetRoleParam extends PageParam { +interface RoleGetParam extends PageParam { searchName?: string searchRegex?: boolean } @@ -215,3 +215,17 @@ interface RoleChangeStatusParam { id: string enable: boolean } + +interface RoleAddEditParam { + id?: string + name: string + powerIds: number[] + enable: boolean +} + +interface PowerSetVo { + moduleList: ModuleVo[] + menuList: MenuVo[] + elementList: ElementVo[] + operationList: OperationVo[] +} diff --git a/src/pages/system/Role.tsx b/src/pages/system/Role.tsx index a7be892..a402c92 100644 --- a/src/pages/system/Role.tsx +++ b/src/pages/system/Role.tsx @@ -1,20 +1,33 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import FitFullScreen from '@/components/common/FitFullScreen' import HideScrollbar from '@/components/common/HideScrollbar' import FlexBox from '@/components/common/FlexBox' import Card from '@/components/common/Card' -import { r_changeRoleStatus, r_getRole } from '@/services/system.tsx' +import { + r_addRole, + r_changeRoleStatus, + r_getPower, + r_getRole, + r_updateRole +} from '@/services/system.tsx' import { COLOR_ERROR_SECONDARY, COLOR_FONT_SECONDARY, COLOR_PRODUCTION, - DATABASE_SELECT_SUCCESS + DATABASE_DUPLICATE_KEY, + DATABASE_INSERT_SUCCESS, + DATABASE_SELECT_SUCCESS, + DATABASE_UPDATE_SUCCESS } from '@/constants/common.constants.ts' import Icon from '@ant-design/icons' +import { powerListToPowerTree } from '@/utils/common.ts' const Role: React.FC = () => { + const [form] = AntdForm.useForm() + const formValues = AntdForm.useWatch([], form) + const [newFormValues, setNewFormValues] = useState() const [roleData, setRoleData] = useState([]) - const [loading, setLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false) const [tableParams, setTableParams] = useState({ pagination: { current: 1, @@ -23,8 +36,14 @@ const Role: React.FC = () => { } }) const [searchName, setSearchName] = useState('') - const [useRegex, setUseRegex] = useState(false) + const [isUseRegex, setIsUseRegex] = useState(false) const [isRegexLegal, setIsRegexLegal] = useState(true) + const [isDrawerOpen, setIsDrawerOpen] = useState(false) + const [isDrawerEdit, setIsDrawerEdit] = useState(false) + const [submittable, setSubmittable] = useState(false) + const [powerTreeData, setPowerTreeData] = useState<_DataNode[]>([]) + const [isLoadingPower, setIsLoadingPower] = useState(false) + const [isSubmitting, setIsSubmitting] = useState(false) const dataColumns: _ColumnsType = [ { @@ -68,7 +87,12 @@ const Role: React.FC = () => { 启用 )} - 编辑 + + 编辑 + 删除 @@ -101,10 +125,101 @@ const Role: React.FC = () => { } } + const handleOnAddBtnClick = () => { + setIsDrawerEdit(false) + setIsDrawerOpen(true) + form.setFieldValue('id', undefined) + form.setFieldValue('name', newFormValues?.name) + form.setFieldValue('powerIds', newFormValues?.powerIds) + form.setFieldValue('enable', newFormValues?.enable ?? true) + } + + const handleOnEditBtnClick = (value: RoleWithPowerGetVo) => { + return () => { + setIsDrawerEdit(true) + setIsDrawerOpen(true) + form.setFieldValue('id', value.id) + form.setFieldValue('name', value.name) + form.setFieldValue( + 'powerIds', + value.operations.map((operation) => operation.powerId) + ) + form.setFieldValue('enable', value.enable) + void form.validateFields() + } + } + + const handleOnDrawerClose = () => { + setIsDrawerOpen(false) + } + + const handleOnSubmit = () => { + if (isSubmitting) { + return + } + + setIsSubmitting(true) + + if (isDrawerEdit) { + void r_updateRole(formValues) + .then((res) => { + const data = res.data + switch (data.code) { + case DATABASE_UPDATE_SUCCESS: + void message.success({ + content: '更新成功' + }) + setIsDrawerOpen(false) + getRole() + break + case DATABASE_DUPLICATE_KEY: + void message.error({ + content: '已存在相同名称的角色' + }) + break + default: + void message.error({ + content: '更新失败,请稍后重试' + }) + } + }) + .finally(() => { + setIsSubmitting(false) + }) + } else { + void r_addRole(formValues) + .then((res) => { + const data = res.data + switch (data.code) { + case DATABASE_INSERT_SUCCESS: + void message.success({ + content: '添加成功' + }) + setNewFormValues(undefined) + setIsDrawerOpen(false) + getRole() + break + case DATABASE_DUPLICATE_KEY: + void message.error({ + content: '已存在相同名称的角色' + }) + break + default: + void message.error({ + content: '添加失败,请稍后重试' + }) + } + }) + .finally(() => { + setIsSubmitting(false) + }) + } + } + const handleOnSearchNameChange = (e: React.ChangeEvent) => { setSearchName(e.target.value) - if (useRegex) { + if (isUseRegex) { try { RegExp(e.target.value) setIsRegexLegal(!(e.target.value.includes('{}') || e.target.value.includes('[]'))) @@ -123,7 +238,7 @@ const Role: React.FC = () => { } const handleOnUseRegexChange = (e: _CheckboxChangeEvent) => { - setUseRegex(e.target.checked) + setIsUseRegex(e.target.checked) if (e.target.checked) { try { RegExp(searchName) @@ -142,12 +257,30 @@ const Role: React.FC = () => { const handleOnChangStatusBtnClick = (id: string, newStatus: boolean) => { return () => { + if (isLoading) { + return + } + + setIsLoading(true) void r_changeRoleStatus({ id, enable: newStatus }) + .then((res) => { + const data = res.data + if (data.code === DATABASE_UPDATE_SUCCESS) { + getRole() + } else { + void message.error({ + content: '更新失败,请稍后重试' + }) + } + }) + .finally(() => { + setIsLoading(false) + }) } } const getRole = () => { - if (loading) { + if (isLoading) { return } @@ -158,7 +291,7 @@ const Role: React.FC = () => { return } - setLoading(true) + setIsLoading(true) void r_getRole({ currentPage: tableParams.pagination?.current, @@ -170,7 +303,7 @@ const Role: React.FC = () => { sortOrder: tableParams.sortField && tableParams.sortOrder ? tableParams.sortOrder : undefined, searchName: searchName.trim().length ? searchName : undefined, - searchRegex: useRegex ? useRegex : undefined, + searchRegex: isUseRegex ? isUseRegex : undefined, ...tableParams.filters }) .then((res) => { @@ -179,69 +312,12 @@ const Role: React.FC = () => { const records = data.data?.records records?.map((value) => { - const menuMap = new Map() - const elementMap = new Map() - const operationMap = new Map() - - value.operations.forEach((operation) => { - if ( - operationMap.has(operation.elementId) && - operationMap.get(operation.elementId) !== null - ) { - operationMap - .get(operation.elementId) - ?.push({ title: operation.name, key: operation.powerId }) - } else { - operationMap.set(operation.elementId, [ - { title: operation.name, key: operation.powerId } - ]) - } - }) - - value.elements.forEach((element) => { - if ( - elementMap.has(element.menuId) && - elementMap.get(element.menuId) !== null - ) { - elementMap.get(element.menuId)?.push({ - title: element.name, - key: element.powerId, - children: operationMap.get(element.id) - }) - } else { - elementMap.set(element.menuId, [ - { - title: element.name, - key: element.powerId, - children: operationMap.get(element.id) - } - ]) - } - }) - - value.menus.forEach((menu) => { - if (menuMap.has(menu.moduleId) && menuMap.get(menu.moduleId) !== null) { - menuMap.get(menu.moduleId)?.push({ - title: menu.name, - key: menu.powerId, - children: elementMap.get(menu.id) - }) - } else { - menuMap.set(menu.moduleId, [ - { - title: menu.name, - key: menu.powerId, - children: elementMap.get(menu.id) - } - ]) - } - }) - - value.tree = value.modules.map((module) => ({ - title: module.name, - key: module.powerId, - children: menuMap.get(module.id) - })) + value.tree = powerListToPowerTree( + value.modules, + value.menus, + value.elements, + value.operations + ) return value }) @@ -262,10 +338,66 @@ const Role: React.FC = () => { } }) .finally(() => { - setLoading(false) + setIsLoading(false) }) } + const getPowerTreeData = () => { + if (isLoadingPower) { + return + } + + setIsLoadingPower(true) + + void r_getPower() + .then((res) => { + const data = res.data + + if (data.code === DATABASE_SELECT_SUCCESS) { + const powerSet = data.data + powerSet && + setPowerTreeData( + powerListToPowerTree( + powerSet.moduleList, + powerSet.menuList, + powerSet.elementList, + powerSet.operationList + ) + ) + } else { + void message.error({ + content: '获取权限列表失败,请稍后重试' + }) + } + }) + .finally(() => { + setIsLoadingPower(false) + }) + } + + useEffect(() => { + form.validateFields({ validateOnly: true }).then( + () => { + setSubmittable(true) + }, + () => { + setSubmittable(false) + } + ) + + if (!isDrawerEdit && formValues) { + setNewFormValues({ + name: formValues.name, + powerIds: formValues.powerIds, + enable: formValues.enable + }) + } + }, [formValues]) + + useEffect(() => { + getPowerTreeData() + }, []) + useEffect(() => { getRole() }, [ @@ -287,7 +419,11 @@ const Role: React.FC = () => { - + { ) : undefined} .* @@ -340,13 +476,70 @@ const Role: React.FC = () => { columns={dataColumns} rowKey={(record) => record.id} pagination={tableParams.pagination} - loading={loading} + loading={isLoading} onChange={handleOnTableChange} /> + + + + + + + + 取消 + + + 提交 + + + } + > + + + + + + + + + + + + + ) } diff --git a/src/services/system.tsx b/src/services/system.tsx index 68bdc2e..1afd058 100644 --- a/src/services/system.tsx +++ b/src/services/system.tsx @@ -1,11 +1,17 @@ import request from '@/services/index' -import { URL_API_SYS_LOG, URL_API_SYS_ROLE } from '@/constants/urls.constants' +import { URL_API_SYS_LOG, URL_API_SYS_POWER, URL_API_SYS_ROLE } from '@/constants/urls.constants' -export const r_getSysLog = (param: GetSysLogParam) => +export const r_getSysLog = (param: SysLogGetParam) => request.get>(URL_API_SYS_LOG, { ...param }) -export const r_getRole = (param: GetRoleParam) => +export const r_getRole = (param: RoleGetParam) => request.get>(URL_API_SYS_ROLE, { ...param }) export const r_changeRoleStatus = (param: RoleChangeStatusParam) => request.patch(URL_API_SYS_ROLE, { ...param }) + +export const r_getPower = () => request.get(URL_API_SYS_POWER) + +export const r_addRole = (param: RoleAddEditParam) => request.post(URL_API_SYS_ROLE, { ...param }) + +export const r_updateRole = (param: RoleAddEditParam) => request.put(URL_API_SYS_ROLE, { ...param }) diff --git a/src/services/user.tsx b/src/services/user.tsx index 96bbe1c..fc9a977 100644 --- a/src/services/user.tsx +++ b/src/services/user.tsx @@ -1,6 +1,6 @@ import request from '@/services' -import { URL_API_USER_INFO, URL_API_USER_LIST } from '@/constants/urls.constants' +import { URL_API_SYS_USER_INFO, URL_API_SYS_USER } from '@/constants/urls.constants' -export const r_getInfo = () => request.get(URL_API_USER_INFO) +export const r_getInfo = () => request.get(URL_API_SYS_USER_INFO) -export const r_getUserList = () => request.get(URL_API_USER_LIST) +export const r_getUserList = () => request.get(URL_API_SYS_USER) diff --git a/src/utils/common.ts b/src/utils/common.ts index 84f4e30..1edeeeb 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -157,3 +157,116 @@ export const floorNumber = (num: number, digits: number) => { } } } + +export const powerListToPowerTree = ( + modules: ModuleVo[], + menus: MenuVo[], + elements: ElementVo[], + operations: OperationVo[] +): _DataNode[] => { + const menuMap = new Map() + const elementMap = new Map() + const operationMap = new Map() + + operations.forEach((operation) => { + if ( + operationMap.has(operation.elementId) && + operationMap.get(operation.elementId) !== null + ) { + operationMap.get(operation.elementId)?.push({ + title: operation.name, + fullTitle: operation.name, + key: operation.powerId, + value: operation.powerId + }) + } else { + operationMap.set(operation.elementId, [ + { + title: operation.name, + fullTitle: operation.name, + key: operation.powerId, + value: operation.powerId + } + ]) + } + }) + + elements.forEach((element) => { + if (elementMap.has(element.menuId) && elementMap.get(element.menuId) !== null) { + elementMap.get(element.menuId)?.push({ + title: element.name, + fullTitle: element.name, + key: element.powerId, + value: element.powerId, + children: operationMap.get(element.id)?.map((value) => { + value.fullTitle = `${element.name}-${value.fullTitle}` + return value + }) + }) + } else { + elementMap.set(element.menuId, [ + { + title: element.name, + fullTitle: element.name, + key: element.powerId, + value: element.powerId, + children: operationMap.get(element.id)?.map((value) => { + value.fullTitle = `${element.name}-${value.fullTitle}` + return value + }) + } + ]) + } + }) + + menus.forEach((menu) => { + if (menuMap.has(menu.moduleId) && menuMap.get(menu.moduleId) !== null) { + menuMap.get(menu.moduleId)?.push({ + title: menu.name, + fullTitle: menu.name, + key: menu.powerId, + value: menu.powerId, + children: elementMap.get(menu.id)?.map((value) => { + value.fullTitle = `${menu.name}-${value.fullTitle}` + value.children?.forEach((value1) => { + value1.fullTitle = `${menu.name}-${value1.fullTitle}` + }) + return value + }) + }) + } else { + menuMap.set(menu.moduleId, [ + { + title: menu.name, + fullTitle: menu.name, + key: menu.powerId, + value: menu.powerId, + children: elementMap.get(menu.id)?.map((value) => { + value.fullTitle = `${menu.name}-${value.fullTitle}` + value.children?.forEach((value1) => { + value1.fullTitle = `${menu.name}-${value1.fullTitle}` + }) + return value + }) + } + ]) + } + }) + + return modules.map((module) => ({ + title: module.name, + fullTitle: module.name, + key: module.powerId, + value: module.powerId, + children: menuMap.get(module.id)?.map((value) => { + value.fullTitle = `${module.name}-${value.fullTitle}` + value.children?.forEach((value1) => { + value1.fullTitle = `${module.name}-${value1.fullTitle}` + value1.children?.forEach((value2) => { + value2.fullTitle = `${module.name}-${value2.fullTitle}` + }) + }) + return value + }) + })) +}