ant design pro 的表分层级如何处理

在这里插入图片描述

如上图这样,经常我们要加一些分类表,但是这些表是有层次的,比如父级,这种应该如何来表达得更好呢。

在这里插入图片描述
首先得存数据了

后端得先处理好。

我是这样弄的。

一般我存一个 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;

在这里插入图片描述
我们拥有 12 年建站编程经验

  1. 虚拟产品交易平台定制开发
  2. WordPress 外贸电商独立站建站

我的网站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员随风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值