【图文并茂】ant design pro 如何优雅奇妙地把 crud 的 api 单独抽出来共用

news2024/11/17 23:48:12

在这里插入图片描述
我们写后台项目,经常要写增删改查的接口。

比如 角色 权限

我们不可能都写一个 api

比如 getRoles, getPermissions

这些请求列表的,都是一样的,只是路径不同

那么我们应该抽出来,放到一起,直接去调,只要传不同的路径就行

比如 /api/users, /api/permissions

抽出

列表页

export async function queryList(
  url: string,
  params?: { [key: string]: any },
  sort?: { [key: string]: any },
  filter?: { [key: string]: any },
) {
  return request<API.DataList>(url, {
    method: 'GET',
    params: {
      ...params,
      page: params!.current,
      limit: params!.pageSize,
      sorter: sort,
      ...filter,
    },
  });
}

如果调用?

request={async (params, sort, filter) => queryList('/menus', params, sort, filter)}

其它

export async function addItem(url: string, options?: { [key: string]: any }) {
  return request<API.ItemData>(url, {
    method: 'POST',
    data: {
      ...(options || {}),
    },
  });
}

export async function updateItem(url: string, options?: { [key: string]: any }) {
  return request<API.ListItem>(url, {
    method: 'PUT',
    data: {
      ...(options || {}),
    },
  });
}

export async function handleItem(url: string, options?: { [key: string]: any }) {
  return request<API.ResponseData>(url, {
    method: 'PATCH',
    data: {
      ...(options || {}),
    },
  });
}

export async function removeItem(url: string, options?: { [key: string]: any }) {
  return request<Record<string, any>>(url, {
    method: 'DELETE',
    data: {
      ...(options || {}),
    },
  });
}

如何调呢?

/**
 * @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('/menus', { ...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(`/menus/${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('/menus', {
      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;
  }
};

完整代码:

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('/menus', { ...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(`/menus/${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('/menus', {
      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: menus, loading } = useQueryList('/menus');

  /**
   * @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_category' }),
      dataIndex: ['parent', 'name'],
      // @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: 'select_parent_category' })}
            allowClear
            treeNodeFilterProp="name"
            fieldNames={{ label: 'name', value: '_id' }}
            treeDefaultExpandAll
            treeData={menus}
            loading={loading}
            {...fieldProps}
          />
        );
      },
    },
    {
      title: intl.formatMessage({ id: 'path' }),
      dataIndex: 'path',
      renderFormItem: (item, { ...rest }) => {
        return <ProFormText {...rest} placeholder={intl.formatMessage({ id: 'enter_path' })} />;
      },
    },
    {
      title: intl.formatMessage({ id: 'permission' }),
      dataIndex: 'permission',
      hideInSearch: true,
      renderText: (val: any) => val && val.name,
    },
    {
      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 && (
            <Button
              type="primary"
              key="primary"
              onClick={() => {
                handleModalOpen(true);
              }}
            >
              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
            </Button>
          ),
        ]}
        request={async (params, sort, filter) => queryList('/menus', 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.canDeleteMenu) && (
            <DeleteButton
              onOk={async () => {
                await handleRemove(selectedRowsState?.map((item: any) => item._id!));
                setSelectedRows([]);
                actionRef.current?.reloadAndRest?.();
              }}
            />
          )}
        </FooterToolbar>
      )}
      {(access.canSuperAdmin || access.canCreateMenu) && (
        <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.canUpdateMenu) && (
        <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;

完结。


  • 获取 ant design pro & nodejs & typescript 多角色权限动态菜单管理系统源码*
  • 我正在做的程序员赚钱副业 - Shopify 真实案例技术赚钱营销课视频教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双向电表是什么电表?为什么光伏发电储能要求安装双向电能表!

双向电表是什么电表? 双向电表就在用电的时候假如是正转&#xff0c;那么向外送电的时候就是反转&#xff0c;也就是读数越来越小。反总是指反向总有功&#xff0c;反无是指反向总无功。 双向电表&#xff0c;也称为双向计量电能表&#xff0c;是一种能够计量用电和发电的电…

第三节:Nodify 添加连接关系

引言 Nodify有三层结构&#xff0c;编辑器Editor&#xff0c;节点Node和连接组件Connection&#xff0c;上节介绍了节点和编辑器&#xff0c;本节介绍连接组件。连接组件用于保存节点中连接端子的连接关系&#xff0c;并随节点的拖动改变。 1、连接组件 连接组件存储一个连接关…

海外媒体软文发稿【越南通讯社vnanet】官方媒体发布新闻稿

海外媒体软文发稿【越南通讯社vnanet】官方媒体发布新闻稿 越南通讯社(越南语&#xff1a;Thng tấn x Việt Nam&#xff1b;英语&#xff1a;Vietnam News Agency&#xff0c;简称VNA)&#xff0c;简称“越通社”是越南国家通讯社&#xff0c;始建于1945年9月2日。越通社是越…

JVM的内存模型和垃圾回收

JVM内存区域 内存模型图&#xff1a; 堆 线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。 它的目的是存放对象实例。同时它也是GC所管理的主要区域&#xff0c;因此常被称为GC堆&#xff0c;又由于现在收集器常使用分代算法&#xff0c;Java堆中还…

纷享销客AI能力在线索精细化管理中的应用与实践

1、智能评分提高线索转换效率 企业可以经过后台模型配置&#xff0c;和对近1-2年内线索的相关属性进行整理分析&#xff0c;纷享销客AI可以总结出历史相似线索的转换概率&#xff0c;使销售人员可以集中精力在更容易转换的线索上&#xff0c;提高转化效率。 纷享销客AI的智能…

pdf如何转换为jpg图片?这几种转换方法简单又实用!

pdf如何转换为jpg图片&#xff1f;PDF&#xff0c;这一广泛应用于文档传输与存储的格式&#xff0c;虽极大地促进了信息的电子化流通&#xff0c;但在日常办公实践中&#xff0c;也逐渐显露出其局限性&#xff0c;尤其是在文件管理与网络分享方面&#xff0c;PDF的庞大体积常导…

fastadmin 时间选择器可选择具体的时分秒

预期要达到的样式,时间区间可以直接选择具体的时分秒 找到这个文件require-form.js 在pulic/assets/js 下面 找到datetimepicker 属性 设置为true var options {timePicker: true,autoUpdateInput: false,timePickerSeconds: true,timePicker24Hour: true,autoApply: true,lo…

Llama 3.1 70B与Mistral Large 2 128B深度对比

在人工智能的浩瀚宇宙中&#xff0c;两颗新星正在引发行业内的轰动。Meta 的 Llama-3.1-70B 和Mistral Large-2-128B&#xff0c;这两大 AI 巨头以其前所未有的计算能力和复杂性&#xff0c;正引领着智能算法的新浪潮。它们不仅仅是技术的集大成者&#xff0c;更是未来可能性的…

基于微信小程序的点餐小程序/基于微信小程序的订餐系统设计与实现

微信点餐小程序 摘 要 随着互联网技术不断地发展&#xff0c;网络成为了人们生活的一部分&#xff0c;而点餐系统作为网上应用的一个全新的体现&#xff0c;由于其特有的便捷性&#xff0c;已经被人们所接受。目前主流的点餐服务不仅不明确并且管理员管理起来不容易&#xff0…

公司最大的内卷,偷偷做单元测试

一位读者在看过我的《理解这八大优势&#xff0c;才算精通单元测试》后&#xff0c;问我&#xff1a;知道单元测试有好处&#xff0c;但实在没空写。看完文章后又想重新落实一下&#xff0c;有没有啥写好单元测试的技巧&#xff1f; 这位读者绝对不是第一个和我抱怨单元测试的…

安卓窗口window无法移除屏幕外超过屏幕边界?-wms源码层面深入剖析

背景 学习了上一节的窗口位置变化相关的内容后&#xff0c;在窗口移动过程过程中发现有一个限制问题&#xff0c;大家可以看一下如下动态图&#xff1a; 已经尽力把窗口想要拖到屏幕外面&#xff0c;但是一直拖到不生效&#xff0c;只能在屏幕内部进行移动&#xff0c;这个到…

智能叮咚门铃的功能,开启未来家居安全新篇章

在科技日新月异的今天&#xff0c;智能家居产品正逐步渗透到我们生活的每一个角落&#xff0c;其中&#xff0c;智能叮咚门铃作为家庭安防与便捷生活的重要一环&#xff0c;正经历着前所未有的功能升级与变革。 一、高清夜视&#xff0c;全天候守护 全新智能叮咚门铃配备了高清…

芒果/充电桩系统云快充1.5底层协议源码(源码)

充电桩系统云快充1.5底层协议源码&#xff08;源码&#xff09; 介绍 云快充协议云快充1.5协议云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩系统桩直连协议 软件架构 1、提供云快充底层桩直连协议&#xff0c;版本为云快充1.5&#xff0c;对于没有对接过…

网上都是Python淘汰了!Python没用了!为什么都看不上Python?

最近&#xff0c;看到网上好多人站在在职程序员的角度去分析编程语言的一个优劣&#xff0c;劝小白学这个语言别学那个语言&#xff0c;这对小白来说是毫无意义的。 但是它又具有极强的一个误导性。 针对“Python没用了&#xff1f;马上就要被淘汰啦&#xff1f;为什么这么多…

8月22日笔记

解决centos7本地服务器刚安装之后yum install -y wget出现问题情况 首先网络能ping得通&#xff0c;然后就是yum命令会出问题&#xff0c;网上很多方法都是用wget命令来解决的&#xff0c;但是本身就没有wget&#xff0c;我怎么解决&#x1f605;。还有就是改/etc/sysconfig/n…

推荐并整理一波vscode插件(哪些内置了,哪些好用)

文章目录 背景现在还在用的&#xff08;21款&#xff09;Chinese(Simplified)简体中文Chinese LoremLorem ipsumCode Runner&#xff08;很推荐&#xff09;Codeium: AI Coding Autocomplete&#xff08;推荐&#xff09;Draw.io IntegrationESLintHighlight Matching TagJavaS…

Window访问Linux目录权限问题

Linux 上已经安装 了 samba 服务&#xff0c;但有时会发现从window上无法打开一些目录&#xff0c;点击后没有反应也没有弹窗提示&#xff0c;如&#xff1a; 而且当用 sourceinsight 添加文件时&#xff0c;这些目录下也搜索不到任何文件&#xff0c;其原因是目录权限问题。注…

AI绘图 | Stable Diffusion教程,零基础上手(附最新最全安装包)

前言 通过Stable Diffusion技术&#xff0c;一个人可以毫不费力地实现令人惊叹的AI绘图&#xff0c;让创意和想象力跃然纸上。这项技术运用先进的深度学习模型&#xff0c;将简单的文字描述转化为精美绝伦的艺术作品。无需深厚的绘画功底或昂贵的设备&#xff0c;只需输入一段…

开放式耳机哪个品牌好?分享四款开放式蓝牙耳机排行榜前十名

我相信很多人都会有这些问题&#xff0c;不知道入手什么蓝牙耳机品牌、有线耳机不好收纳、有线耳机不方便携带、蓝牙耳机听歌的音质怎么样、蓝牙耳机是否会对大脑有危害、蓝牙耳机有什么品牌型号推荐以及想要不同价位的蓝牙耳机品牌推荐参考&#xff0c;okok问题也是很多&#…

【数据结构】关于快速排序,归并排序,计数排序,基数排序,你到底了解多少???(超详解)

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;这期继续讲解排序算法的原理&#xff0c;希望你能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/g7PyB &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlct…