需求分析
需求:(1)在左侧菜单新增自己对应名词的菜单--例: 老师列表-张三(2)添加对应菜单的页面,老师列表页,提供老师信息的查询,新增功能
列表查询页
分两部分:搜索区域(对应参数入参)+列表区域(对应响应参数)
要求:包含参数中要求的控件组件,最终发起请求的参数齐全,响应成功展示正常即可
新增页面
新增页面以弹窗形式完成
要求:包含参数中要求的控件组件,最终发起请求的参数齐全
查询接口
import {addTea, getTeaList} from '@/services/ant-design-pro/api';
addTea为新增老师接口, getTeaList为查询列表接口
查询列表接口参数
新增老师接口参数(入参)
查询老师列表响应体
{
success:true,
pageSize: 5,
total:100,
data:[
{name:‘张三’,
iPhone:10086,
type:1,
timeList: [1651110000000,1651110314000],
onJob:true,
remarks: “张三是个好老师”
}
], //数组中的对象为Teacher
}
Teacher对象字段
新增老师响应体
{
status:200,
}
功能实现
实现新增
实现删除/修改、查询
实现对列表的搜索查询功能
高级表格自带的其它功能
例1:
index.tsx页面代码
import React, { useState, useRef } from 'react';
import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
import { Button, message, Menu, Dropdown, Form } from 'antd';
import type { ProColumns, ActionType } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import request from 'umi-request';
import ProForm, {
ModalForm,
ProFormText,
ProFormTextArea,
ProFormSelect,
ProFormDateTimePicker,
} from '@ant-design/pro-form';
import { useIntl } from 'umi';
import { addTea, getTeaList } from '@/services/ant-design-pro/api';
import '@ant-design/pro-table/dist/table.css';
import type { RecordKey } from '@ant-design/pro-utils/lib/useEditableArray';
const columns: ProColumns[] = [
{
dataIndex: 'index',
valueType: 'indexBorder',
width: 48,
},
{
title: '名称',
dataIndex: 'name',
copyable: true,
ellipsis: true,
formItemProps: {
},
},
{
title: '手机号',
dataIndex: 'iPhone',
copyable: true,
ellipsis: true,
},
{
disable: true,
title: '老师类型',
dataIndex: 'type',
filters: true,
onFilter: true,
valueType: 'select',
valueEnum: {
0: {
text: '语文',
},
1: {
text: '数学',
},
2: {
text: '英语',
},
},
},
{
disable: true,
title: '是否在职',
dataIndex: 'onJob',
filters: true,
onFilter: true,
hideInSearch: true,
valueType: 'select',
valueEnum: {
true: {
text: '是',
status: 'Success',
},
false: {
text: '否',
status: 'Error',
},
},
},
{
title: '创建时间',
key: 'createTime',
dataIndex: 'createTime',
valueType: 'dateTime',
sorter: true,
search: {
transform: (value) => {
return {
createTime: new Date(value).getTime(),
};
},
},
},
{
title: '结束时间',
key: 'endTime',
dataIndex: 'endTime',
valueType: 'dateTime',
sorter: true,
search: {
transform: (value) => {
return {
endTime: new Date(value).getTime(),
};
},
},
},
{
disable: true,
title: '备注',
dataIndex: 'remarks',
search: false,
renderFormItem: (_, { defaultRender }) => {
return defaultRender(_);
},
},
{
title: '操作',
valueType: 'option',
key: 'option',
render: (text, record, _, action) => [
{
action?.startEditable?.(record.key);
}}
>
编辑
,
],
},
];
const menu = (
);
/**
* handleAdd
* @param fields
*/
const handleAdd = async (fields: API.teaListItem) => {
const hide = message.loading('正在添加');
// handleList(fields)
try {
await addTea({ ...fields });
hide();
message.success('Added successfully');
return true;
} catch (error) {
hide();
message.error('Adding failed, please try again!');
return false;
}
};
getTeaList;
const hyhList: React.FC = () => {
const actionRef = useRef();
const [createModalVisible, handleModalVisible] = useState(false);
const [form] = Form.useForm();
const intl = useIntl();
return (
columns={columns}
actionRef={actionRef}
cardBordered
request={async (params = {}, sort, filter) => {
console.log(sort, filter);
console.log(params, '***params');
let listdata: any;
listdata = await request<{
data: API.teaListItem[];
}>('/api/tea/List', {
params,
});
return Promise.resolve(listdata);
}}
editable={{
type: 'multiple',
onSave: (key: RecordKey, row: API.teaListItem) => {
console.log(key, row, 'onSave');
return Promise.resolve();
},
onDelete: (key: RecordKey, row: API.teaListItem) => {
console.log(key, row, 'onDelete');
return Promise.resolve();
},
}}
columnsState={{
persistenceKey: 'pro-table-singe-demos',
persistenceType: 'localStorage',
onChange(value) {
console.log('value: ', value);
},
}}
rowKey="key"
search={{
labelWidth: 'auto',
}}
pagination={{
pageSize: 5,
onChange: (page) => console.log(page),
}}
dateFormatter="string"
headerTitle="高级表格"
toolBarRender={() => [
}
type="primary"
onClick={() => {
handleModalVisible(true);
}}
>
新增老师
,
,
{
//debugger;
const success = await handleAdd(value as API.teaListItem);
if (success) {
handleModalVisible(false);
if (actionRef.current) {
actionRef.current.reload();
form.resetFields();
}
}
}}
>
[
{
value: 0,
label: '数学',
},
{
value: 1,
label: '语文',
},
{
value: 2,
label: '英语',
},
]}
width="xs"
name="type"
label="请选择老师类型"
/>
[
{
value: 'true',
label: '是',
},
{
value: 'false',
label: '否',
},
]}
width="xs"
name="onJob"
label="请选择是否在任职"
/>
{
return {
createTime: Date.parse(value),
};
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
{
return {
endTime: Date.parse(value),
};
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
,
]}
/>
);
};
export default hyhList;
api配置
import { request } from 'umi';
import requestumi from 'umi-request';
/** 新增老师 /api/addTea */
export async function addTea(options?: { [key: string]: any }) {
console.log(options);
return requestumi<{
data: [];
}>('/api/tea/add', {
method: 'POST',
params: {
...options,
},
...(options || {}),
});
}
export async function getTeaList(options?: { [key: string]: any }) {
return requestumi('/api/tea/list', {
method: 'GET',
params: {
...options,
},
...(options || {}),
});
}
mock数据处理
import { Request, Response } from 'express';
import { parse } from 'url';
// mock tableListDataSource
const genList = (current: number, pageSize: number) => {
const tableListDataSource: API.teaListItem[] = [];
for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
tableListDataSource.push({
key: index,
onJob: i % 6 === 0,
name: `张三 ${index}`,
iPhone: Math.floor(Math.random() * 1000),
type: i % 3,
remarks: '这是一段描述',
createTime: new Date().getTime(),
endTime: new Date().getTime(),
});
}
tableListDataSource.reverse();
return tableListDataSource;
};
let tableListDataSource = genList(1, 100);
function getTeaList(req: Request, res: Response, u: string) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const { current = 1, pageSize = 10 } = req.query;
const params = parse(realUrl, true).query as unknown as API.PageParams &
API.teaListItem &
API.RuleListItem & {
sorter: any;
filter: any;
};
let dataSource = [...tableListDataSource].slice(
((current as number) - 1) * (pageSize as number),
(current as number) * (pageSize as number),
);
if (params.sorter) {
const sorter = JSON.parse(params.sorter);
dataSource = dataSource.sort((prev, next) => {
let sortNumber = 0;
Object.keys(sorter).forEach((key) => {
if (sorter[key] === 'descend') {
if (prev[key] - next[key] > 0) {
sortNumber += -1;
} else {
sortNumber += 1;
}
return;
}
if (prev[key] - next[key] > 0) {
sortNumber += 1;
} else {
sortNumber += -1;
}
});
return sortNumber;
});
}
if (params.filter) {
const filter = JSON.parse(params.filter as any) as {
[key: string]: string[];
};
if (Object.keys(filter).length > 0) {
dataSource = dataSource.filter((item) => {
return Object.keys(filter).some((key) => {
if (!filter[key]) {
return true;
}
if (filter[key].includes(`${item[key]}`)) {
return true;
}
return false;
});
});
}
}
//搜索查询
if (params.name) {
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
}
if (params.iPhone) {
dataSource = dataSource.filter((data) =>
data?.iPhone?.toString().includes(params.iPhone.toString()),
);
}
if (params.type) {
dataSource = dataSource.filter((data) =>
data?.type?.toString().includes(params.type.toString()),
);
}
if (params.createTime) {
dataSource = dataSource.filter(
(data) => data?.createTime,
// data?.createTime >= params.createTime,
);
}
if (params.endTime) {
dataSource = dataSource.filter(
(data) => data?.endTime,
//data?.endTime <= params.endTime,
);
}
const result = {
data: dataSource,
total: tableListDataSource.length,
success: true,
pageSize,
current: parseInt(`${params.current}`, 10) || 1,
};
return res.json(result);
}
function addTea(req: Request, res: Response, u: string, b: Request) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const { pageSize = 1 } = req.query;
const params = parse(realUrl, true).query as unknown as API.PageParams &
API.teaListItem &
API.RuleListItem & {
sorter: any;
filter: any;
};
let dataSource = [...tableListDataSource];
for (let i = 0; i < pageSize; i += 1) {
tableListDataSource.unshift({
key: tableListDataSource.length,
onJob: params.onJob,
name: params.name,
iPhone: params.iPhone,
type: params.type,
remarks: params.remarks,
createTime: new Date().setTime(params.createTime),
endTime: new Date().setTime(params.endTime),
});
}
const resultes = {
data: dataSource,
total: tableListDataSource.length,
success: true,
pageSize,
current: parseInt(`${params.current}`, 10) || 1,
};
return res.json({ status: 200, data: resultes });
}
export default {
'GET /api/tea/List': getTeaList,
'POST /api/tea/add': addTea,
};
typing.d.ts 类型声明文件
declare namespace API {
type teaListItem = {
key: index,
onJob: boolean,
name: string,
iPhone: number,
type: number,
remarks: string,
createTime: number,
endTime: number,
};
}
proxy.ts文件
export default {
dev: {
'/api/': {
// 要代理的地址
//target: 'https://preview.pro.ant.design',
changeOrigin: true,
},
}};
routes.ts文件
export default [
{
name: 'bunny.list',
icon: 'table',
path: '/hyhList',
component: './hyhList',
},]
menu.ts文件(位置:src/locales/zh-CN/..)
export default {
'menu.bunny.list': '老师列表-bunny',
}
另一个比较简单的写法,也是实现admin新增页面,实现增删改查的,与上面的相比差一些,可以用作比较,有异同之处。例2:
api-代码
//--查询列表 mock获取-- (ok)
export const getTeaList = async (params: any) => {
return request('/api/list', { params: params })
}
//--新增老师接口 mock获取--(ok)
export const addTea = async (params: any) => {
console.log(params);
return request('/api/add', {
params: params
})
}
mock-代码
//方法一:指定给定返回数据的参数
// let list = [
// {
// id: 1,
// name: 'bunny',
// iPhone: '86688',
// type: '数学',
// createTime: 1650931200000,
// endTime: 1651276800000,
// onJob: 'true',
// remarks: '他是一位好老师',
// },
// ]
//方法二:通过for循环push上去
const genList = (current: number, pageSize: number) => {
let list = []
for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
list.push({
id: index,
onJob: i % 6 === 0,
name: `bunny ${index}`,
iPhone: Math.floor(Math.random() * 1000),
type: i % 3,
remarks: '描述说明...',
createTime: new Date().getTime(),
endTime: new Date().getTime(),
});
}
list.reverse();
return list;
};
let list = genList(1, 100);
export default {
'GET /api/list': (req: any, res: any) => {
let query = req.query;
//过滤查询
let dataListSource: any
function filterQuery(list: any) {
let dataList: any[] = []
for (let i in query) {
let lists = list.filter((item: any) => {
return item[i] === query[i]
})
dataList = [...dataList, ...lists]
dataListSource = dataList
}
res.json({ status: 200, data: Array.from(new Set(dataList)), pageSize: 5, total: list.length })
}
if (JSON.stringify(query) === '{}') {
res.json({ status: 200, data: list, pageSize: 5, total: list.length })
} else {
//过滤查询
filterQuery(list)
}
},
'GET /api/add': (req: any, res: any) => {
//添加name
// console.log(req);
// res.end('ok')
// res.end(JSON.stringify(req.body)) //req.body 拿到json数据
let timeFun = (tiems: string) => {
let d = new Date(tiems)
let k = d.getTime()
console.log(k)
return k
}
const item: any = {
id: list.length + 1,
name: req.query.name,
iPhone: req.query.iPhone,
type: Number(req.query.type),
createTime: timeFun(req.query.createTime),
endTime: timeFun(req.query.endTime),
onJob: req.query.onJob,
remarks: req.query.remarks,
}
list.unshift(item)
//list.push(item)
// res.end({ status: 200 })
res.json({ status: 200, data: list })
}
}
index.tsx-代码
import { PlusOutlined, EllipsisOutlined } from '@ant-design/icons';
import { Button, Menu, Dropdown, message } from 'antd';
import type { ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { useState, useEffect } from 'react';
import ProForm, {
ModalForm, ProFormText, ProFormDateRangePicker, ProFormSelect,
} from '@ant-design/pro-form';
import { addTea, getTeaList } from '@/services/ant-design-pro/api'
import type { RecordKey } from '@ant-design/pro-utils/lib/useEditableArray';
import request from 'umi-request';
type TeacherIssueItem = {
name: string;//名称
iPhone: number;//手机号
type: number;//老师类型(状态)
createTime: number;//创建时间
endTime: number;//结束时间
onJob: boolean;//是否在职
remarks: string;//备注
id: number;
};
const columns: ProColumns[] = [
{
dataIndex: 'index',
valueType: 'indexBorder',
width: 48,
},
{
title: '名 称',
dataIndex: 'name',
copyable: true,
ellipsis: true,
},
{
title: '手机号',
dataIndex: 'iPhone',
copyable: true,
ellipsis: true,
},
{
disable: true,
title: '老师类型',
dataIndex: 'type',
filters: true,
onFilter: true,
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
0: {
text: '语文',
},
1: {
text: '数学',
},
2: {
text: '英语',
},
},
},
{
title: '创建时间',
key: 'showTime',
dataIndex: 'createTime',
valueType: 'dateTime',
sorter: true,
hideInSearch: true,
},
{
title: '创建时间',
dataIndex: 'createTime',
valueType: 'dateRange',
hideInTable: true,
search: {
transform: (value) => {
return {
startTime: value[0],
endTime: value[1],
};
},
},
},
{
title: '结束时间',
key: 'showTime',
dataIndex: 'endTime',
valueType: 'dateTime',
sorter: true,
hideInSearch: true,
},
{
title: '结束时间',
dataIndex: 'endTime',
valueType: 'dateRange',
hideInTable: true,
hideInSearch: true,
search: {
transform: (value) => {
return {
startTime: value[0],
endTime: value[1],
};
},
},
},
{
disable: true,
title: '是否在职',
dataIndex: 'onJob',
filters: true,
onFilter: true,
hideInSearch: true,
valueType: 'select',
valueEnum: {
true: {
text: '是',
status: 'Success',
},
false: {
text: '否',
status: 'Error',
},
},
},
{
title: '备 注',
dataIndex: 'remarks',
hideInSearch: true,
copyable: true,
ellipsis: true,
},
{
title: '操作列表',
valueType: 'option',
key: 'option',
render: (text, record, _, action) => [
{
action?.startEditable?.(record.id);
}}
>
编辑
,
],
},
];
const menu = (
);
// 数据过滤
function filterData(lists: any) {
// lists.forEach((ele: any) => {
// switch (ele.type) {
// case 1:
// ele.type = '数学'
// break;
// case 2:
// ele.type = '语文'
// break;
// case 3:
// ele.type = '英语'
// break;
// case null:
// ele.type = '--'
// default:
// break;
// }
// })
return lists
}
export default () => {
let [list, setList] = useState([]);
useEffect(async () => {
if (list.length <= 0) {
let lists = await getTeaList({})
setList(filterData(lists.data))
}
}, [])
// //--新增老师数据请求--
const handleForm = async (values: any) => {
console.log(values, "values");
//获取表单新增的数据
let obj = {
createTime: values.contractTime ? values.contractTime[0] : '--',
endTime: values.contractTime ? values.contractTime[1] : '--',
name: values.name ? values.name : '--',
type: values.type ? values.type : '--',
iPhone: values.iPhone ? values.iPhone : '--',
onJob: values.onJob ? values.onJob : '--',
remarks: values.remarks ? values.remarks : '--'
}
// 请求获取响应数据,并将新增的数据返回到api接口给后端存储
const res = await addTea(obj)
//如果响应状态为200,则是获取成功。添加成功后要刷新列表数据
if (res.status = 200) {
//获取最新的列表数据
let lists = await getTeaList({})
//将数据更新渲染出来
setList(filterData(lists.data))
message.success(res.message)
} else {
message.error(res.message)
}
return true
}
//--添加--搜索
const beforeSearchSubmit = (val: any) => {
getTeaList({ ...val }).then((res) => {
setList(filterData(res.data))
})
}
return (
columns={columns}
cardBordered
dataSource={list}
// request={ async() =>list}
// request={async (params) => {
// console.log(params, "params");
// let data: any
// data = await request('/api/List');
// console.log(data, "data");//data:[Array]
// return Promise.resolve(data);//将列表渲染到页面
// // return request<{
// // }>('/api/List', {
// // }).then((res) => {
// // console.log(res, ".then的res");//data:[Array]
// // });
// }}
editable={{
type: 'multiple',
onSave: (key: RecordKey, row: TeacherIssueItem & {
index?: number | undefined;
}) => {
console.log(key, row, 'onSave');
return Promise.resolve();
},
onDelete: (key: RecordKey, row: TeacherIssueItem & {
index?: number | undefined;
}
) => {
console.log(key, row, 'onDelete');
return Promise.resolve();
}
}}
columnsState={{
persistenceKey: 'pro-table-singe-demos',
persistenceType: 'localStorage',
onChange(value) {
console.log('value: ', value);
},
}}
beforeSearchSubmit={beforeSearchSubmit}
rowKey="id"
search={{
labelWidth: 'auto',
}}
form={{
// 由于配置了 transform,提交的参与与定义的不同这里需要转化一下
syncToUrl: (values, type) => {
if (type === 'get') {
return {
...values,
//开始时间
createTime: [values.startTime, values.endTime],
};
}
return values;
},
}}
pagination={{
pageSize: 5,
onChange: (page) => console.log(page)
// onChange: onChange,
}}
dateFormatter="string"
headerTitle="高级表格"
toolBarRender={() => [
title="新增老师"
trigger={
} type="primary">
新增老师
}
autoFocusFirstInput
modalProps={{
onCancel: () => console.log('run'),
}}
submitTimeout={2000}
onFinish={value => handleForm(value)}
>
[
{
value: 1,
label: '数学',
},
{
value: 2,
label: '语文',
},
{
value: 3,
label: '英语',
},
]}
width="xs"
name="type"
label="请选择老师类型"
/>
[
{
value: 'true',
label: '是',
},
{
value: 'false',
label: '否',
}
]}
width="xs"
name="onJob"
label="请选择是否在任职"
/>
,
,
]}
/>
);
};
其它小知识点的笔记
1. 请求接口,响应数据
(1).then和await的区别:
request={async (params) => {
console.log(params, "params");
return request<{
}>('/api/List', {
// params,//去掉传参
}).then((res) => {
console.log(res, ".then的res");
});
}}
request={async (params) => {
console.log(params, "params");
let data: any
data = await request('/api/List');
console.log(data, "data");//data:[Array]
return Promise.resolve(data);//将列表渲染到页面
// return request<{
// }>('/api/List', {
// }).then((res) => {
// console.log(res, ".then的res");
// });
}}
也可以用表单自带的dataSource来获取请求的数据,如例2里就是这样实现的。
--
2. 使用antd实现 表单输入完成后重置默认状态(也就是清空原来已经输入的值)
import { Form } from 'antd';
const hyhList: React.FC = () => {
const [form] = Form.useForm();//重置表格
return (
{
if (success) {
form.resetFields();//重置
}
}
)
}
export default hyhList;
3. 使用mock转化为时间戳
但是,这样的话,在
let timeFun = (tiems: string) => {
let d = new Date(tiems)
let k = d.getTime()
console.log(k)
return k
}
const item: any = {
createTime: timeFun(req.query.createTime),
endTime: timeFun(req.query.endTime),
}
但列表所得的时间格式是时间戳格式:
4.在新增表单中,获取新增数据后转化为时间戳再返回给mock处理时间转化为时间日期格式再返回表单页面
{
return {
createTime: Date.parse(value),
};
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
{
return {
endTime: Date.parse(value),
};
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
5. ProFormDateTimePicker和 fieldProps的使用
value.format('YYYY-MM-DD'),
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
value.format('YYYY-MM-DD'),
}}
rules={[{ required: true, message: 'Please select your country!' }]}
/>
6. index.tsx的页面结构部署
7. 编辑的用法,实现删除和保存
{
title: '操作',
valueType: 'option',
key: 'option',
render: (text, record, _, action) => [
{
action?.startEditable?.(record.key);
}}
>
编辑
,
],
},
editable={{
type: 'multiple',
onSave: (key: RecordKey, row: API.teaListItem) => {
console.log(key, row, 'onSave');
return Promise.resolve();
},
onDelete: (key: RecordKey, row: API.teaListItem) => {
console.log(key, row, 'onDelete');
return Promise.resolve();
},
}}
8. 想要新增的数据显示在第一条 有两种方式
(1)list.push({
//data数据...
})
list.reverse();
return list;
(2)直接把push()方法 改为 const item={//data数据...} list.unshift(item)