Browse Source

完善角色页

main
dark 5 months ago
parent
commit
7094e03ae1
  1. 1
      src/locales/lang/zh-CN.ts
  2. 1
      src/pages/system/menus/components/MenuTree.tsx
  3. 1
      src/pages/system/menus/index.tsx
  4. 7
      src/pages/system/menus/store.ts
  5. 133
      src/pages/system/roles/index.tsx
  6. 38
      src/pages/system/roles/store.ts
  7. 1
      src/types/roles.d.ts
  8. 54
      src/utils/tree.ts

1
src/locales/lang/zh-CN.ts

@ -63,6 +63,7 @@ export default {
success: '提交成功', success: '提交成功',
fail: '提交失败', fail: '提交失败',
saveSuccess: '保存成功', saveSuccess: '保存成功',
editSuccess: '修改成功',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
saveFail: '保存失败', saveFail: '保存失败',
emptyData: '暂无数据', emptyData: '暂无数据',

1
src/pages/system/menus/components/MenuTree.tsx

@ -36,6 +36,7 @@ const MenuTree = ({ form }: { form: FormInstance }) => {
return () => { return () => {
setCurrentMenu({} as MenuItem) setCurrentMenu({} as MenuItem)
setIds([])
} }
}, [ data, isLoading ]) }, [ data, isLoading ])

1
src/pages/system/menus/index.tsx

@ -33,7 +33,6 @@ const Menus = () => {
description: (error as any).message ?? t('message.saveFail', '保存失败'), description: (error as any).message ?? t('message.saveFail', '保存失败'),
}) })
} }
}, [ isError, isSuccess ]) }, [ isError, isSuccess ])
return ( return (

7
src/pages/system/menus/store.ts

@ -60,7 +60,8 @@ export const saveOrUpdateMenuAtom = atomWithMutation<IApiResult<IMenu>>((get) =>
return await systemServ.menus.update(data) return await systemServ.menus.update(data)
}, },
onSuccess: (res) => { onSuccess: (res) => {
message.success(t('message.saveSuccess', '保存成功'))
const isAdd = !!res.data?.id
message.success(t( isAdd ? 'message.saveSuccess': 'message.editSuccess', '保存成功'))
const menu = get(selectedMenuAtom) const menu = get(selectedMenuAtom)
store.set(selectedMenuAtom, { store.set(selectedMenuAtom, {
...menu, ...menu,
@ -69,7 +70,7 @@ export const saveOrUpdateMenuAtom = atomWithMutation<IApiResult<IMenu>>((get) =>
//更新列表 //更新列表
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix // @ts-ignore fix
get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then()
get(queryClientAtom).invalidateQueries([ 'menus', get(menuPageAtom) ]).then()
} }
} }
}) })
@ -85,7 +86,7 @@ export const deleteMenuAtom = atomWithMutation((get) => {
onSuccess: () => { onSuccess: () => {
message.success(t('message.deleteSuccess', '删除成功')) message.success(t('message.deleteSuccess', '删除成功'))
store.set(batchIdsAtom, []) store.set(batchIdsAtom, [])
get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then()
get(queryClientAtom).invalidateQueries([ 'menus', get(menuPageAtom) ]).then()
} }
} }
}) })

133
src/pages/system/roles/index.tsx

@ -1,33 +1,89 @@
import { ActionType, PageContainer, ProColumns, ProTable,BetaSchemaForm } from '@ant-design/pro-components'
import {
ActionType,
PageContainer,
ProColumns,
ProTable,
BetaSchemaForm,
} from '@ant-design/pro-components'
import { createLazyFileRoute } from '@tanstack/react-router' import { createLazyFileRoute } from '@tanstack/react-router'
import { useStyle } from './style.ts' import { useStyle } from './style.ts'
import { useMemo, useRef, useState } from 'react'
import { useAtom, useAtomValue } from 'jotai'
import { roleAtom, rolesAtom } from './store.ts'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { pageAtom, roleAtom, rolesAtom, saveOrUpdateRoleAtom, searchAtom } from './store.ts'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Form, Space, Table } from 'antd'
import { Button, Form, Space, Spin, Table, Tree } from 'antd'
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
import { menuDataAtom } from '@/pages/system/menus/store.ts'
import { getTreeCheckedStatus } from '@/utils/tree.ts'
const MenuTree = (props: any) => {
const { data: menuList, isLoading: menuLoading } = useAtomValue(menuDataAtom)
const { value, onChange, form, id, mode } = props
const onCheck = (checkedKeys: any, info: any) => {
if (onChange) {
onChange([ ...checkedKeys, ...info.halfCheckedKeys ])
} else {
form.setFieldsValue({ [id]: [ ...checkedKeys, ...info.halfCheckedKeys ] })
}
}
if (menuLoading) {
return <Spin spinning={true} size={'small'}/>
}
return <Tree treeData={menuList}
fieldNames={{ title: 'title', key: 'id' }}
disabled={mode !== 'edit'} checkable={true} onCheck={onCheck}
checkedKeys={getTreeCheckedStatus(menuList, value)}/>
}
const Roles = () => { const Roles = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { styles } = useStyle() const { styles } = useStyle()
const [form] = Form.useForm()
const [ form ] = Form.useForm()
const actionRef = useRef<ActionType>() const actionRef = useRef<ActionType>()
const { data, isLoading } = useAtomValue(rolesAtom)
const [ role, setRole ] = useAtom(roleAtom)
const [ open, setOpen] = useState(false)
const [ page, setPage ] = useAtom(pageAtom)
const setSearch = useSetAtom(searchAtom)
const { data, isLoading, isFetching, refetch } = useAtomValue(rolesAtom)
const { isPending, mutate, isSuccess } = useAtomValue(saveOrUpdateRoleAtom)
const [ , setRole ] = useAtom(roleAtom)
const [ open, setOpen ] = useState(false)
const columns = useMemo(() => { const columns = useMemo(() => {
return [ return [
{ title: 'id', dataIndex: 'id', hideInTable: true, hideInSearch: true, hideInForm: true,},
{ title: '名称', dataIndex: 'name', valueType: 'text',},
{ title: '别名', dataIndex: 'code', valueType: 'text', },
{
title: 'id', dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
// hideInForm: true,
},
{
title: '名称', dataIndex: 'name', valueType: 'text',
formItemProps: {
rules: [ { required: true, message: '请输入角色名称' } ]
}
},
{
title: '别名', dataIndex: 'code', valueType: 'text',
formItemProps: {
rules: [ { required: true, message: '请输入别名' } ]
}
},
{ title: '状态', dataIndex: 'status', valueType: 'switch', }, { title: '状态', dataIndex: 'status', valueType: 'switch', },
{ title: '排序', dataIndex: 'sort', valueType: 'indexBorder', },
{ title: '备注', dataIndex: 'remark' ,valueType: 'textarea' },
{ title: '权限', dataIndex: 'permissions', valueType: 'treeSelect', },
{
title: '排序', dataIndex: 'sort', valueType: 'digit',
},
{ title: '备注', dataIndex: 'description', valueType: 'textarea' },
{
title: '权限', dataIndex: 'menu_ids',
valueType: 'text',
renderFormItem: (item, config, form) => {
return <MenuTree {...config} form={form} {...item.fieldProps} />
}
},
{ {
title: '操作', valueType: 'option', title: '操作', valueType: 'option',
key: 'option', key: 'option',
@ -35,7 +91,6 @@ const Roles = () => {
<a <a
key="editable" key="editable"
onClick={() => { onClick={() => {
// action?.startEditable?.(record.id)
setRole(record) setRole(record)
setOpen(true) setOpen(true)
form.setFieldsValue(record) form.setFieldsValue(record)
@ -51,6 +106,12 @@ const Roles = () => {
] as ProColumns[] ] as ProColumns[]
}, []) }, [])
useEffect(() => {
if (isSuccess) {
setOpen(false)
}
}, [ isSuccess ])
return ( return (
<PageContainer breadcrumbRender={false} title={false} className={styles.container}> <PageContainer breadcrumbRender={false} title={false} className={styles.container}>
<ProTable <ProTable
@ -58,35 +119,40 @@ const Roles = () => {
actionRef={actionRef} actionRef={actionRef}
headerTitle={t('system.roles.title', '角色管理')} headerTitle={t('system.roles.title', '角色管理')}
columns={columns} columns={columns}
loading={isLoading}
loading={isLoading || isFetching}
dataSource={data?.rows} dataSource={data?.rows}
search={false} search={false}
rowSelection={{ rowSelection={{
selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ],
}} }}
tableAlertOptionRender={() => { tableAlertOptionRender={() => {
return ( return (
<Space size={16}> <Space size={16}>
<a></a> <a></a>
</Space> </Space>
);
)
}}
options={{
reload: () => {
refetch()
},
}} }}
toolbar={{ toolbar={{
search: { search: {
onSearch: (value: string) => { onSearch: (value: string) => {
alert(value)
setSearch({ name: value })
}, },
}, },
onSearch: (value) => {
console.log('value', value)
},
actions: [ actions: [
<Button <Button
key="button" key="button"
icon={<PlusOutlined/>} icon={<PlusOutlined/>}
onClick={() => { onClick={() => {
actionRef.current?.reload()
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}} }}
type="primary" type="primary"
> >
@ -96,10 +162,13 @@ const Roles = () => {
}} }}
pagination={{ pagination={{
total: data?.total, total: data?.total,
current: data?.page,
pageSize: data?.pageSize,
current: page.page,
pageSize: page.pageSize,
onChange: (page) => { onChange: (page) => {
console.log('page', page)
setPage((prev) => {
return { ...prev, page }
})
} }
}} }}
/> />
@ -117,10 +186,14 @@ const Roles = () => {
maskClosable: false, maskClosable: false,
}} }}
onOpenChange={(open) => { onOpenChange={(open) => {
console.log('open', open)
setOpen(open) setOpen(open)
}} }}
columns={columns} />
loading={isPending}
onFinish={(values) => {
// console.log('values', values)
return mutate(values)
}}
columns={columns}/>
</PageContainer> </PageContainer>
) )
} }

38
src/pages/system/roles/store.ts

@ -1,8 +1,10 @@
import { atom } from 'jotai/index' import { atom } from 'jotai/index'
import { IRole } from '@/types/roles' import { IRole } from '@/types/roles'
import { atomWithQuery } from 'jotai-tanstack-query'
import { IPage, IPageResult } from '@/types'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import { IApiResult, IPage, IPageResult } from '@/types'
import systemServ from '@/service/system.ts' import systemServ from '@/service/system.ts'
import { message } from 'antd'
import { t } from '@/i18n.ts'
type SearchParams = IPage & IRole type SearchParams = IPage & IRole
@ -14,6 +16,10 @@ export const roleAtom = atom<IRole>(undefined as unknown as IRole)
export const searchAtom = atom<SearchParams>({} as SearchParams) export const searchAtom = atom<SearchParams>({} as SearchParams)
export const pageAtom = atom<IPage>({
pageSize: 10,
page: 1,
})
export const rolesAtom = atomWithQuery<any, IPageResult<IRole>>((get) => { export const rolesAtom = atomWithQuery<any, IPageResult<IRole>>((get) => {
return { return {
@ -22,4 +28,30 @@ export const rolesAtom = atomWithQuery<any, IPageResult<IRole>>((get) => {
return await systemServ.role.list(params as SearchParams) return await systemServ.role.list(params as SearchParams)
}, },
} }
})
})
//saveOrUpdateRoleAtom
export const saveOrUpdateRoleAtom = atomWithMutation<IApiResult<IRole>>((get) => {
return {
mutationKey: [ 'updateMenu' ],
mutationFn: async (data: IRole) => {
if (data.id === 0) {
return await systemServ.menus.add(data)
}
return await systemServ.menus.update(data)
},
onSuccess: (res) => {
const isAdd = !!res.data?.id
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功'))
//更新列表
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix
get(queryClientAtom).invalidateQueries([ 'roles', get(searchAtom) ]).then()
return res
}
}
})

1
src/types/roles.d.ts

@ -1,5 +1,6 @@
export interface IRole { export interface IRole {
id: number,
sort: number, sort: number,
code: string, code: string,
name: string, name: string,

54
src/utils/tree.ts

@ -0,0 +1,54 @@
type TreeKey = string | number;
interface TreeNode {
key: TreeKey;
id?: TreeKey;
children?: TreeNode[];
}
export function getTreeCheckedStatus(tree: TreeNode[], selectKeys: TreeKey[]): { checked: TreeKey[], halfChecked: TreeKey[] } {
const checked: TreeKey[] = []
const halfChecked: TreeKey[] = []
if (!tree || tree.length === 0) return { checked, halfChecked }
if (!selectKeys || selectKeys.length === 0) return { checked, halfChecked }
// 辅助函数来递归地检查每个节点
function checkNode(node: TreeNode, ancestors: TreeKey[]): void {
const key = node.key ?? node.id
const isLeaf = !node.children || node.children.length === 0
const isSelected = selectKeys.includes(key)
// 如果是叶节点并且被选中,则直接加入到checked数组
if (isLeaf && isSelected) {
checked.push(key)
// 标记所有祖先为半选状态,除非它们已经被完全选中
ancestors.forEach(ancestorKey => {
if (!halfChecked.includes(ancestorKey) && !checked.includes(ancestorKey)) {
halfChecked.push(ancestorKey)
}
})
return
}
// 非叶节点,递归检查其子节点
if (node.children) {
const childAncestors = [ ...ancestors, key ]
node.children.forEach(child => checkNode(child, childAncestors))
// 检查当前节点的所有子节点是否全部或部分被选中
const childSelectedCount = node.children.filter(child => checked.includes(child.key ?? child.id)).length
if (childSelectedCount === node.children.length) {
// 如果所有子节点都被选中,将当前节点标为全选
checked.push(key)
} else if (childSelectedCount > 0) {
// 如果部分子节点被选中,将当前节点标为半选
halfChecked.push(key)
}
}
}
// 遍历每一个根节点
tree.forEach(node => checkNode(node, []))
return { checked, halfChecked }
}
Loading…
Cancel
Save