世界上最伟大的投资就是投资自己的教育
ant design pro 的表分层级如何处理
随风发布于134 次阅读
- ant design pro 如何去保存颜色
- ant design pro v6 如何做好角色管理
- ant design 的 tree 如何作为角色中的权限选择之一
- ant design 的 tree 如何作为角色中的权限选择之二
- ant design pro access.ts 是如何控制多角色的权限的
- ant design pro 中用户的表单如何控制多个角色
- ant design pro 如何实现动态菜单带上 icon 的
如上图这样,经常我们要加一些分类表,但是这些表是有层次的,比如父级,这种应该如何来表达得更好呢。
后端得先处理好。
我是这样弄的。
一般我存一个 parent ,指向父级就好,这样能通过 parent 找到所有的 children.
当然你要两边都存也行,但是每次修改都要操作就比较麻烦,但胜在读取数据,得到 children 比较简单。
我一般只存 parent.
import mongoose, { Document } from 'mongoose';
export interface IPermissionGroup extends Document {
name: string;
parent?: IPermissionGroup;
children?: IPermissionGroup[];
createdAt?: Date;
updatedAt?: Date;
}
const permissionGroupSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true },
parent: { type: mongoose.Schema.Types.ObjectId, ref: 'PermissionGroup' },
},
{ timestamps: true },
);
const PermissionGroup = mongoose.model<IPermissionGroup>(
'PermissionGroup',
permissionGroupSchema,
);
export default PermissionGroup;
这里是没有存 children 的。但是需要把 children 返回给前端的。
// 获取权限组列表
const getPermissionGroups = handleAsync(async (req: Request, res: Response) => {
const { current = '1', pageSize = '10' } = req.query;
const query = buildQuery(req.query);
// 执行查询
const permissionGroups = await PermissionGroup.find(query)
.populate('parent') // Assuming you want to populate parent
.sort('-createdAt') // Sort by creation time in descending order
.skip((+current - 1) * +pageSize)
.limit(+pageSize)
.exec();
const total = await PermissionGroup.countDocuments(query).exec();
const getChildren = async (parentId: string | null): Promise<any[]> => {
const children = await PermissionGroup.find({ parent: parentId })
.populate('parent')
.exec();
return Promise.all(
children.map(async (child) => ({
...child.toObject(),
children: await getChildren(child._id),
})),
);
};
const getPermissionGroupsWithChildren = async (
permissionGroups: any[],
): Promise<any[]> => {
return Promise.all(
permissionGroups.map(async (permissionGroup) => ({
...permissionGroup.toObject(),
children: await getChildren(permissionGroup._id),
})),
);
};
const permissionGroupsWithChildren =
await getPermissionGroupsWithChildren(permissionGroups);
res.json({
success: true,
data: permissionGroupsWithChildren,
total,
current: +current,
pageSize: +pageSize,
});
});
多加了个循环,但胜在数据不会太多,没啥问题。
返回给前端的数据是这样的:
{
"success": true,
"data": [
{
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0,
"children": [
{
"_id": "66b1b00bb5d937a0aef34034",
"name": "权限",
"createdAt": "2024-08-06T05:09:31.292Z",
"updatedAt": "2024-08-10T02:24:41.759Z",
"__v": 0,
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"children": []
},
{
"_id": "66b6d2c9b9ad87dfa985f34f",
"name": "用户",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-10T02:39:05.563Z",
"updatedAt": "2024-08-10T02:39:05.563Z",
"__v": 0,
"children": []
},
{
"_id": "66b6d2ddb9ad87dfa985f362",
"name": "菜单",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-10T02:39:25.628Z",
"updatedAt": "2024-08-10T02:39:25.628Z",
"__v": 0,
"children": []
},
{
"_id": "66b6d2e9b9ad87dfa985f377",
"name": "角色",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-10T02:39:37.339Z",
"updatedAt": "2024-08-10T02:39:37.339Z",
"__v": 0,
"children": []
},
{
"_id": "66b6d2fdb9ad87dfa985f38e",
"name": "数据权限",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-10T02:39:57.756Z",
"updatedAt": "2024-08-10T02:39:57.756Z",
"__v": 0,
"children": []
},
{
"_id": "66b6d314b9ad87dfa985f3a7",
"name": "权限组",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-10T02:40:20.528Z",
"updatedAt": "2024-08-10T02:40:20.528Z",
"__v": 0,
"children": []
},
{
"_id": "66b9ad348554e602536acc67",
"name": "认证管理菜单",
"parent": {
"_id": "66b1b54ef8871ea52a7e3de9",
"name": "认证管理",
"createdAt": "2024-08-06T05:31:58.495Z",
"updatedAt": "2024-08-10T02:24:31.070Z",
"__v": 0
},
"createdAt": "2024-08-12T06:35:32.560Z",
"updatedAt": "2024-08-12T06:35:32.560Z",
"__v": 0,
"children": []
}
]
},
{
"_id": "66adec30d647a4fde5546b1c",
"name": "材料类目",
"createdAt": "2024-08-03T08:37:04.433Z",
"updatedAt": "2024-08-10T02:24:51.188Z",
"__v": 0,
"children": []
}
],
"total": 2,
"current": 1,
"pageSize": 20
}
children 就是显示出层次。
import { useIntl } from '@umijs/max';
import { addItem, queryList, removeItem, updateItem } from '@/services/ant-design-pro/api';
import { PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components';
import { FooterToolbar, PageContainer, ProFormText, ProTable } from '@ant-design/pro-components';
import { FormattedMessage, useAccess } from '@umijs/max';
import { Button, message, TreeSelect } from 'antd';
import React, { useRef, useState } from 'react';
import type { FormValueType } from './components/Update';
import Update from './components/Update';
import Create from './components/Create';
import useQueryList from '@/hooks/useQueryList';
import Show from './components/Show';
import DeleteButton from '@/components/DeleteButton';
import DeleteLink from '@/components/DeleteLink';
/**
* @en-US Add node
* @zh-CN 添加节点
* @param fields
*/
const handleAdd = async (fields: API.ItemData) => {
const hide = message.loading(<FormattedMessage id="adding" defaultMessage="Adding..." />);
try {
await addItem('/permission-groups', { ...fields });
hide();
message.success(<FormattedMessage id="add_successful" defaultMessage="Added successfully" />);
return true;
} catch (error: any) {
hide();
message.error(
error?.response?.data?.message ?? (
<FormattedMessage id="upload_failed" defaultMessage="Upload failed, please try again!" />
),
);
return false;
}
};
/**
* @en-US Update node
* @zh-CN 更新节点
*
* @param fields
*/
const handleUpdate = async (fields: FormValueType) => {
const hide = message.loading(<FormattedMessage id="updating" defaultMessage="Updating..." />);
try {
await updateItem(`/permission-groups/${fields._id}`, fields);
hide();
message.success(<FormattedMessage id="update_successful" defaultMessage="Update successful" />);
return true;
} catch (error: any) {
hide();
message.error(
error?.response?.data?.message ?? (
<FormattedMessage id="update_failed" defaultMessage="Update failed, please try again!" />
),
);
return false;
}
};
/**
* Delete node
* @zh-CN 删除节点
*
* @param selectedRows
*/
const handleRemove = async (ids: string[]) => {
const hide = message.loading(<FormattedMessage id="deleting" defaultMessage="Deleting..." />);
if (!ids) return true;
try {
await removeItem('/permission-groups', {
ids,
});
hide();
message.success(
<FormattedMessage
id="delete_successful"
defaultMessage="Deleted successfully and will refresh soon"
/>,
);
return true;
} catch (error: any) {
hide();
message.error(
error.response.data.message ?? (
<FormattedMessage id="delete_failed" defaultMessage="Delete failed, please try again" />
),
);
return false;
}
};
const TableList: React.FC = () => {
const intl = useIntl();
/**
* @en-US Pop-up window of new window
* @zh-CN 新建窗口的弹窗
* */
const [createModalOpen, handleModalOpen] = useState<boolean>(false);
/**2024fc.xyz
* @en-US The pop-up window of the distribution update window
* @zh-CN 分布更新窗口的弹窗
* */
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
// const [batchUploadPriceModalOpen, setBatchUploadPriceModalOpen] = useState<boolean>(false);
const actionRef = useRef<ActionType>();
const [currentRow, setCurrentRow] = useState<API.ItemData>();
const [selectedRowsState, setSelectedRows] = useState<API.ItemData[]>([]);
const [showDetail, setShowDetail] = useState<boolean>(false);
const access = useAccess();
const { items: permissionGroup, loading } = useQueryList('/permission-groups');
/**
* @en-US International configuration
* @zh-CN 国际化配置
* */
// Define roles object with index signature
const columns: ProColumns<API.ItemData>[] = [
{
title: intl.formatMessage({ id: 'name' }),
dataIndex: 'name',
copyable: true,
renderFormItem: (item, { ...rest }) => {
return <ProFormText {...rest} placeholder={intl.formatMessage({ id: 'enter_name' })} />;
},
render: (dom, entity) => {
return (
<a
onClick={() => {
setCurrentRow(entity);
setShowDetail(true);
}}
>
{dom}
</a>
);
},
},
{
title: intl.formatMessage({ id: 'parent_permissionGroup' }),
dataIndex: ['parent', 'name'],
hideInSearch: true,
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderFormItem: (_, { type, defaultRender, formItemProps, fieldProps, ...rest }, form) => {
if (type === 'form') {
return null;
}
return (
<TreeSelect
showSearch
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder={intl.formatMessage({ id: 'parent_permissionGroup' })}
allowClear
treeNodeFilterProp="name"
fieldNames={{ label: 'name', value: '_id', children: 'children' }}
treeDefaultExpandAll
treeData={permissionGroup}
loading={loading}
{...fieldProps}
/>
);
},
},
{
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="Operating" />,
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
access.canSuperAdmin && (
<a
key="edit"
onClick={() => {
// Replace `handleUpdateModalOpen` and `setCurrentRow` with your actual functions
handleUpdateModalOpen(true);
setCurrentRow(record);
}}
>
{intl.formatMessage({ id: 'edit' })}
</a>
),
access.canSuperAdmin && (
<DeleteLink
onOk={async () => {
await handleRemove([record._id!]);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}}
/>
),
],
},
];
return (
<PageContainer>
<ProTable<API.ItemData, API.PageParams>
headerTitle={intl.formatMessage({ id: 'list' })}
actionRef={actionRef}
rowKey="_id"
search={{
labelWidth: 100,
}}
toolBarRender={() => [
(access.canSuperAdmin || access.canUpdatePermissionGroup) && (
<Button
type="primary"
key="primary"
onClick={() => {
handleModalOpen(true);
}}
>
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
</Button>
),
]}
request={async (params, sort, filter) =>
queryList('/permission-groups', params, sort, filter)
}
columns={columns}
rowSelection={
access.canSuperAdmin && {
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows);
},
}
}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
<FormattedMessage id="pages.searchTable.chosen" defaultMessage="Chosen" />{' '}
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
<FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
</div>
}
>
{(access.canSuperAdmin || access.canDeletePermissionGroup) && (
<DeleteButton
onOk={async () => {
await handleRemove(selectedRowsState?.map((item: any) => item._id!));
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}}
/>
)}
</FooterToolbar>
)}
{(access.canSuperAdmin || access.canCreatePermissionGroup) && (
<Create
open={createModalOpen}
onOpenChange={handleModalOpen}
onFinish={async (value) => {
const success = await handleAdd(value as API.ItemData);
if (success) {
handleModalOpen(false);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
/>
)}
{(access.canSuperAdmin || access.canUpdatePermissionGroup) && (
<Update
onSubmit={async (value) => {
const success = await handleUpdate(value);
if (success) {
handleUpdateModalOpen(false);
setCurrentRow(undefined);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={handleUpdateModalOpen}
updateModalOpen={updateModalOpen}
values={currentRow || {}}
/>
)}
<Show
open={showDetail}
currentRow={currentRow as API.ItemData}
columns={columns as ProDescriptionsItemProps<API.ItemData>[]}
onClose={() => {
setCurrentRow(undefined);
setShowDetail(false);
}}
/>
</PageContainer>
);
};
export default TableList;
表单的话比较简单:
import { useIntl } from '@umijs/max';
import React from 'react';
import { ProForm, ProFormText } from '@ant-design/pro-components';
import { Form, Input } from 'antd';
import PermissionGroupSelect from '@/components/PermissionGroupSelect';
interface Props {
newRecord?: boolean;
onFinish: (formData: any) => Promise<void>;
values?: any;
}
const BasicForm: React.FC<Props> = ({ newRecord, onFinish, values }) => {
const intl = useIntl();
return (
<ProForm
initialValues={{
...values,
parent: values?.parent?._id,
}}
onFinish={async (values) => {
await onFinish({
...values,
});
}}
>
<ProForm.Group>
<ProFormText
rules={[{ required: true, message: intl.formatMessage({ id: 'enter_name' }) }]}
width="md"
label={intl.formatMessage({ id: 'name' })}
name="name"
/>
<PermissionGroupSelect name="parent" label="permission_group" />
</ProForm.Group>
{!newRecord && (
<Form.Item name="_id" label={false}>
<Input type="hidden" />
</Form.Item>
)}
</ProForm>
);
};
export default BasicForm;
initialValues={{
...values,
parent: values?.parent?._id,
}}
这块仍然跟编辑的时候有关,可以填上它的值
PermissionGroupSelect 的源码是这样的:
import React from 'react';
import { ProFormTreeSelect } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import useQueryList from '@/hooks/useQueryList';
const PermissionGroupSelect = ({ name, label }: { name: string; label: string }) => {
const intl = useIntl();
const { items: permissionGroups, loading } = useQueryList('/permission-groups');
return (
<ProFormTreeSelect
name={name}
rules={[{ required: false }]}
width="md"
label={intl.formatMessage({ id: label })}
allowClear
secondary
fieldProps={{
showArrow: false,
treeDefaultExpandAll: true,
filterTreeNode: true,
showSearch: true,
dropdownMatchSelectWidth: false,
autoClearSearchValue: true,
treeNodeFilterProp: 'name',
fieldNames: {
label: 'name',
value: '_id',
children: 'children',
},
treeData: permissionGroups,
loading,
}}
/>
);
};
export default PermissionGroupSelect;
- 虚拟产品交易平台定制开发
- WordPress 外贸电商独立站建站
本站文章均为原创内容,如需转载请注明出处,谢谢。
0 条回复
暂无回复~~
© 汕尾市求知科技有限公司 | Rails365 Gitlab | 知乎 | b 站 | csdn
粤公网安备 44152102000088号 | 粤ICP备19038915号
Top