背景
最近经常有开发前端的需求,就记录下自己是如何一步一步开发一个增删改查的
主要思路
- 创建项目
- 运行项目
- 准备后端API
- 调用API 【分页列表】
- 调用API 【新增】
- 调用API 【修改】
- 调用API 【删除】
- 环境配置
- 容器发布
一. 创建项目
TIPS:建议直接通过命令行创建,最近使用vscode插件创建的项目无法启动
```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…yarn create ice ice-demo
success Installed “create-ice@1.7.5” with binaries:
- 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.
控制键盘上下, 选择 TypeScript + Fusion Design Pro
```latex
? Please select a template TypeScript + Fusion Design Pro
download tarballURL https://registry.npmmirror.com/@alifd/fusion-design-pro/-/fusion-design-pro-0.5.1.tgz
✔ download npm tarball successfully.
clean package.json...
Initialize project successfully.
Starts the development server.
cd ice-demo
npm install
npm start
We have prepared develop toolkit for you.
See: https://marketplace.visualstudio.com/items?itemName=iceworks-team.iceworks
✨ Done in 134.63s.
安装依赖
$ cd ice-demo
$ yarn
二. 运行项目
$ 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 |
三. 准备后端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
import * as React from 'react';
import { ResponsiveGrid } from '@alifd/next';
import PageHeader from '@/components/PageHeader';
const { Cell } = ResponsiveGrid;
const NewsListPage = () => {
return (
<ResponsiveGrid gap={20}>
<Cell colSpan={12}>
<PageHeader
title="新闻列表"
breadcrumbs={[
{ name: '新闻管理' },
{ name: '新闻列表' },
]}
description="展示近期得新闻列表"
/>
</Cell>
<Cell colSpan={12}>
666
</Cell>
</ResponsiveGrid>
);
};
export default NewsListPage;
2. 配置路由页面
const NewsListPage = lazy(() => import('@/pages/NewsListPage'));
// 找到routerConfig 添加
const routerConfig: IRouterConfig[] = [
{
path: '/',
component: BasicLayout,
children: [
{
path: '/news/list',
component: NewsListPage,
},
]
}
]
3. 配置菜单和路由
const asideMenuConfig = [
{
name: '新闻管理',
path: '/',
icon: 'chart-news',
children: [
{
name: '新闻列表',
path: '/news/list',
},
],
},
]
4. 查看效果
5. 核心组件代码
import React, { useCallback } from 'react';
import { Button, Field, Table, Card, Pagination, Message, Dialog } from '@alifd/next';
import { useFusionTable } from 'ahooks';
import { request } from 'ice';
/*
* 获取表格数据
* @param current 第几页
* @param pageSize 每页条数
* @param formData 表格数据
*/
const getTableData = (
{ current, pageSize }: { current: number; pageSize: number },
formData: { status: 'normal' | 'empty' | 'exception' },
): Promise<any> => {
if (!formData.status || formData.status === 'normal') {
let query = `page=${current}&pageSize=${pageSize}`;
Object.entries(formData).forEach(([key, value]) => {
if (value) {
query += `&${key}=${value}`;
}
});
return request(`/api/news?${query}`)
.then(res => ({
total: res.data.total,
list: res.data.list.slice(0, 10),
}));
}
if (formData.status === 'empty') {
return Promise.resolve([]);
}
if (formData.status === 'exception') {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('data exception'));
}, 1000);
});
}
return Promise.resolve([]);
};
const NewsDialogTable: React.FC = () => {
const field = Field.useField([]);
const { paginationProps, tableProps, search, error, refresh } = useFusionTable(getTableData, {
field,
});
const { reset } = search;
const handleDelete = useCallback((data: any) => {
if (!data) {
return;
}
Dialog.confirm({
title: '删除提醒',
content: `确定删除 ${data.title} 吗`,
onOk() {
request({
url: `/api/news/${data.id}`,
method: 'DELETE',
})
.then(res => {
Message.success(`${data.title} 删除成功!`);
reset();
});
},
});
}, [reset]);
const cellOperation = (...args: any[]): React.ReactNode => {
const record = args[2];
return (
<div>
<Button
text
type="primary"
onClick={() => handleDelete(record)}
>
删除
</Button>
</div>
);
};
return (
<div>
<Card free>
<Card.Content>
<Table
{...tableProps}
// onResizeChange={onResizeChange}
// emptyContent={error ? <ExceptionBlock onRefresh={refresh} /> : <EmptyBlock />}
primaryKey="email"
>
<Table.Column title="标题" dataIndex="title" resizable />
<Table.Column title="作者" dataIndex="author" resizable />
<Table.Column title="预览地址" dataIndex="url" resizable />
<Table.Column title="归属地" dataIndex="ip" resizable />
<Table.Column title="内容" dataIndex="content" resizable />
<Table.Column
title="操作"
resizable
cell={cellOperation}
/>
</Table>
<Pagination
style={{ marginTop: 16, textAlign: 'right' }}
totalRender={(total) => (
<>
共{' '}
<Button text type="primary">
{total}
</Button>{' '}
个记录
</>
)}
{...paginationProps}
/>
</Card.Content>
</Card>
</div>
);
};
export default NewsDialogTable;
<Cell colSpan={12}>
<NewsDialogTable />
</Cell>
配置全局请求路径
const appConfig: IAppConfig = {
request: {
baseURL: 'https://console-mock.apipost.cn/app/mock/project/035500cd-6c40-4d49-be88-c3f3fbcd28d3',
},
};
runApp(appConfig);