背景

最近经常有开发前端的需求,就记录下自己是如何一步一步开发一个增删改查的

主要思路

  1. 创建项目
  2. 运行项目
  3. 准备后端API
  4. 调用API 【分页列表】
  5. 调用API 【新增】
  6. 调用API 【修改】
  7. 调用API 【删除】
  8. 环境配置
  9. 容器发布

    一. 创建项目

    TIPS:建议直接通过命令行创建,最近使用vscode插件创建的项目无法启动
    1. yarn create ice ice-demo
    ```latex yarn create v1.22.10 warning package.json: No license field [1/4] 🔍 Resolving packages… [2/4] 🚚 Fetching packages… [3/4] 🔗 Linking dependencies… [4/4] 🔨 Building fresh packages…

success Installed “create-ice@1.7.5” with binaries:

  1. - create-ice

[#####################################################################] 139/139create-ice version: 1.7.5 create-ice args ice-demo undefined ? Please select a template (Use arrow keys) ❯ TypeScript + No UI Components TypeScript + Ant Design TypeScript + Fusion Design TypeScript + Fusion Design Pro JavaScript + Fusion Design ice.js plugin development template.

  1. 控制键盘上下, 选择 TypeScript + Fusion Design Pro
  2. ```latex
  3. ? Please select a template TypeScript + Fusion Design Pro
  4. download tarballURL https://registry.npmmirror.com/@alifd/fusion-design-pro/-/fusion-design-pro-0.5.1.tgz
  5. ✔ download npm tarball successfully.
  6. clean package.json...
  7. Initialize project successfully.
  8. Starts the development server.
  9. cd ice-demo
  10. npm install
  11. npm start
  12. We have prepared develop toolkit for you.
  13. See: https://marketplace.visualstudio.com/items?itemName=iceworks-team.iceworks
  14. ✨ Done in 134.63s.

安装依赖

  1. $ cd ice-demo
  2. $ yarn

等待启动

二. 运行项目

  1. $ yarn start

yarn run v1.22.10 warning ../../../../package.json: No license field $ icejs start ice.js 2.6.4 > Local: http://localhost:3333/ > Network: http://172.100.80.35:3333/ [vite:css] @charset must precede all other statements 4 | body { 5 | -webkit-font-smoothing: antialiased; 6 | } | ^ 7 |

会自动打开浏览器 打开项目 效果图
image.png

三. 准备后端API

使用ApiPost 提供的Mock
https://console-docs.apipost.cn/preview/eb2002184dcd9d95/bd10a0fd2f1333a2?target_id=da14b01d-4617-4448-f662-781a7adecea0#da14b01d-4617-4448-f662-781a7adecea0

四. 调用API 【分页列表】

1. 创建Page

  1. import * as React from 'react';
  2. import { ResponsiveGrid } from '@alifd/next';
  3. import PageHeader from '@/components/PageHeader';
  4. const { Cell } = ResponsiveGrid;
  5. const NewsListPage = () => {
  6. return (
  7. <ResponsiveGrid gap={20}>
  8. <Cell colSpan={12}>
  9. <PageHeader
  10. title="新闻列表"
  11. breadcrumbs={[
  12. { name: '新闻管理' },
  13. { name: '新闻列表' },
  14. ]}
  15. description="展示近期得新闻列表"
  16. />
  17. </Cell>
  18. <Cell colSpan={12}>
  19. 666
  20. </Cell>
  21. </ResponsiveGrid>
  22. );
  23. };
  24. export default NewsListPage;

2. 配置路由页面

  1. const NewsListPage = lazy(() => import('@/pages/NewsListPage'));
  2. // 找到routerConfig 添加
  3. const routerConfig: IRouterConfig[] = [
  4. {
  5. path: '/',
  6. component: BasicLayout,
  7. children: [
  8. {
  9. path: '/news/list',
  10. component: NewsListPage,
  11. },
  12. ]
  13. }
  14. ]

3. 配置菜单和路由

  1. const asideMenuConfig = [
  2. {
  3. name: '新闻管理',
  4. path: '/',
  5. icon: 'chart-news',
  6. children: [
  7. {
  8. name: '新闻列表',
  9. path: '/news/list',
  10. },
  11. ],
  12. },
  13. ]

4. 查看效果

image.png

5. 核心组件代码

  1. import React, { useCallback } from 'react';
  2. import { Button, Field, Table, Card, Pagination, Message, Dialog } from '@alifd/next';
  3. import { useFusionTable } from 'ahooks';
  4. import { request } from 'ice';
  5. /*
  6. * 获取表格数据
  7. * @param current 第几页
  8. * @param pageSize 每页条数
  9. * @param formData 表格数据
  10. */
  11. const getTableData = (
  12. { current, pageSize }: { current: number; pageSize: number },
  13. formData: { status: 'normal' | 'empty' | 'exception' },
  14. ): Promise<any> => {
  15. if (!formData.status || formData.status === 'normal') {
  16. let query = `page=${current}&pageSize=${pageSize}`;
  17. Object.entries(formData).forEach(([key, value]) => {
  18. if (value) {
  19. query += `&${key}=${value}`;
  20. }
  21. });
  22. return request(`/api/news?${query}`)
  23. .then(res => ({
  24. total: res.data.total,
  25. list: res.data.list.slice(0, 10),
  26. }));
  27. }
  28. if (formData.status === 'empty') {
  29. return Promise.resolve([]);
  30. }
  31. if (formData.status === 'exception') {
  32. return new Promise((resolve, reject) => {
  33. setTimeout(() => {
  34. reject(new Error('data exception'));
  35. }, 1000);
  36. });
  37. }
  38. return Promise.resolve([]);
  39. };
  40. const NewsDialogTable: React.FC = () => {
  41. const field = Field.useField([]);
  42. const { paginationProps, tableProps, search, error, refresh } = useFusionTable(getTableData, {
  43. field,
  44. });
  45. const { reset } = search;
  46. const handleDelete = useCallback((data: any) => {
  47. if (!data) {
  48. return;
  49. }
  50. Dialog.confirm({
  51. title: '删除提醒',
  52. content: `确定删除 ${data.title} 吗`,
  53. onOk() {
  54. request({
  55. url: `/api/news/${data.id}`,
  56. method: 'DELETE',
  57. })
  58. .then(res => {
  59. Message.success(`${data.title} 删除成功!`);
  60. reset();
  61. });
  62. },
  63. });
  64. }, [reset]);
  65. const cellOperation = (...args: any[]): React.ReactNode => {
  66. const record = args[2];
  67. return (
  68. <div>
  69. <Button
  70. text
  71. type="primary"
  72. onClick={() => handleDelete(record)}
  73. >
  74. 删除
  75. </Button>
  76. </div>
  77. );
  78. };
  79. return (
  80. <div>
  81. <Card free>
  82. <Card.Content>
  83. <Table
  84. {...tableProps}
  85. // onResizeChange={onResizeChange}
  86. // emptyContent={error ? <ExceptionBlock onRefresh={refresh} /> : <EmptyBlock />}
  87. primaryKey="email"
  88. >
  89. <Table.Column title="标题" dataIndex="title" resizable />
  90. <Table.Column title="作者" dataIndex="author" resizable />
  91. <Table.Column title="预览地址" dataIndex="url" resizable />
  92. <Table.Column title="归属地" dataIndex="ip" resizable />
  93. <Table.Column title="内容" dataIndex="content" resizable />
  94. <Table.Column
  95. title="操作"
  96. resizable
  97. cell={cellOperation}
  98. />
  99. </Table>
  100. <Pagination
  101. style={{ marginTop: 16, textAlign: 'right' }}
  102. totalRender={(total) => (
  103. <>
  104. 共{' '}
  105. <Button text type="primary">
  106. {total}
  107. </Button>{' '}
  108. 个记录
  109. </>
  110. )}
  111. {...paginationProps}
  112. />
  113. </Card.Content>
  114. </Card>
  115. </div>
  116. );
  117. };
  118. export default NewsDialogTable;
  1. <Cell colSpan={12}>
  2. <NewsDialogTable />
  3. </Cell>

配置全局请求路径

  1. const appConfig: IAppConfig = {
  2. request: {
  3. baseURL: 'https://console-mock.apipost.cn/app/mock/project/035500cd-6c40-4d49-be88-c3f3fbcd28d3',
  4. },
  5. };
  6. runApp(appConfig);

6. 效果预览

image.png
image.png

案例代码

参考资源