项目开始
- 前言: 目前新的
umi
是4.x
版本,ant-design-pro
最新有v6.0.0
版本,此项目比较适合新手学习umi
, 有react
基础再学习。 - 新建项目 新建文件夹
umi3-antdpro5-ts
, 下面执行命令yarn create @umijs/umi-app
,版本为:umi 3.5.35 版本
package.json
文件
{
"private": true,
"scripts": {
"start": "umi dev",
"build": "umi build",
"postinstall": "umi generate tmp",
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"test": "umi-test",
"test:coverage": "umi-test --coverage"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,less,md,json}": [
"prettier --write"
],
"*.ts?(x)": [
"prettier --parser=typescript --write"
]
},
"dependencies": {
"@ant-design/pro-layout": "^6.5.0",
"@ant-design/pro-table": "^2.2.7",
"moment": "^2.29.4",
"react": "17.x",
"react-dom": "17.x",
"umi": "^3.5.35"
},
"devDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.5.35",
"lint-staged": "^10.0.7",
"prettier": "^2.2.0",
"typescript": "^4.1.2",
"yorkie": "^2.0.0"
}
}
新建页面
- 在
page
目录 新建文件夹users
- 在
.umirc.ts
自定义路由先注释掉 - 新建文件,文件目录
.
├── components
│ └── UserModal.tsx
├── index.tsx
├── model.ts
├── service.ts
└── type.d.ts
使用自带 AntD 组件
- 整合内置
dva
import React, { FC } from 'react';
import { Button, Popconfirm, Space, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { connect, Dispatch } from 'dva';
import { Loading } from '@@/plugin-dva/connect';
import UserModal from './components/UserModal';
import { SingleUserType, UserModelState } from './type.d';
interface UserPageProps {
users: UserModelState;
dispatch: Dispatch;
useListLoading: boolean;
}
const UsersPageList: FC<UserPageProps> = ({
users,
dispatch,
useListLoading,
}) => {
const [modalVisible, setModalVisible] = React.useState(false);
const [record, setRecord] = React.useState<SingleUserType | undefined>(
undefined,
);
const columns: ColumnsType<SingleUserType> = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
},
{
title: 'CreateTime',
dataIndex: 'create_time',
key: 'create_time',
},
{
title: 'Action',
key: 'action',
render: (_, record: SingleUserType) => (
<Space size="middle">
<a onClick={() => handleEdit(record)}>Edit</a>
<a>
<Popconfirm
title="是否要删除这条记录?"
onConfirm={() => comform(record)}
>
Delete
</Popconfirm>
</a>
</Space>
),
},
];
// 编辑
const handleEdit = (record: SingleUserType) => {
setModalVisible(true);
setRecord(record);
};
// 关闭弹窗
const handleColse = () => {
setModalVisible(false);
setRecord(undefined);
};
// 编辑提交
const onFinish = (values: SingleUserType) => {
let id = 0;
if (record) {
id = record.id;
}
if (id) {
dispatch({
type: 'users/edit',
payload: {
id,
values,
},
});
} else {
dispatch({
type: 'users/add',
payload: {
values,
},
});
}
setModalVisible(false);
};
const handleAdd = () => {
setModalVisible(true);
setRecord(undefined);
};
// 删除
const comform = (record: SingleUserType) => {
dispatch({
type: 'users/del',
payload: {
id: record.id,
},
});
};
// 提交失败
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
return (
<div className="list-table">
<Button type="primary" onClick={handleAdd}>
Add
</Button>
<Table
loading={useListLoading}
columns={columns}
dataSource={users.data}
rowKey="id"
/>
<UserModal
visible={modalVisible}
handleColse={handleColse}
record={record}
onFinish={(values) => {
onFinish(values);
}}
onFinishFailed={(errorInfo) => {
onFinishFailed(errorInfo);
}}
/>
</div>
);
};
const mapStateToProps = ({
users,
loading,
}: {
users: [];
loading: Loading;
}) => {
console.log('users', users);
return {
users,
useListLoading: loading.models.users,
};
};
export default connect(mapStateToProps)(UsersPageList);
import { Effect, Subscription } from 'dva';
import { Reducer } from 'redux';
import { getRemoteList, editRecord, deleteRecord, addRecord } from './service';
import { UserModelState } from './type.d';
export interface UserModelType {
namespace: 'users';
state: UserModelState;
effects: {
query: Effect;
getRemote: Effect;
edit: Effect;
del: Effect;
add: Effect;
};
reducers: {
save: Reducer;
getList: Reducer<UserModelState>;
// 启用 immer 之后
// save: ImmerReducer<UserModelState>;
};
subscriptions: { setup: Subscription };
}
const UserModel: UserModelType = {
namespace: 'users',
state: {
data: [],
meta: {
total: 0,
per_page: 10,
page: 1,
},
},
effects: {
*query({ payload }, { call, put }) {
console.log('query');
},
// 获取远程数据
*getRemote({ payload }, { call, put }): any {
const data = yield call(getRemoteList, payload);
yield put({
type: 'getList',
payload: data,
});
},
// 编辑用户
*edit({ payload }, { call, put }): any {
const data = yield call(editRecord, payload);
// 刷新列表
yield put({
type: 'getRemote',
});
},
//添加用户
*add({ payload }, { call, put }): any {
const data = yield call(addRecord, payload);
// 刷新列表
yield put({
type: 'getRemote',
});
},
// 删除用户
*del({ payload }, { call, put }): any {
const data = yield call(deleteRecord, payload);
// 刷新列表
yield put({
type: 'getRemote',
});
},
},
reducers: {
getList(state, action) {
return {
...state,
...action.payload,
};
},
save(state, action) {
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
return data;
// return {
// ...state,
// ...action.payload,
// };
},
// 启用 immer 之后
// save(state, action) {
// state.name = action.payload;
// },
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/users') {
dispatch({
type: 'getRemote',
});
}
});
},
},
};
export default UserModel;
import { SingleUserType } from './type.d';
import request, { extend } from 'umi-request';
import { message } from 'antd';
const errorHandler = function (error: any) {
if (error.response) {
if (error.response.status > 400) {
message.error(error.data.message ? error.data.message : error.data);
}
console.log(error.response.status);
console.log(error.response.headers);
console.log(error.data);
console.log(error.request);
} else {
console.log(error.message);
message.error('网络异常,请稍后再试');
}
throw error;
};
const extendRequest = extend({ errorHandler });
// 用户列表
export const getRemoteList = async (params: SingleUserType) => {
try {
const data = await extendRequest('/api/users', {
method: 'GET',
params,
});
message.success('列表请求成功');
return data;
} catch (error) {
// message.error(error.message);
console.log(error);
}
};
// 用户编辑
export const editRecord = async ({
id,
values,
}: {
id: number;
values: SingleUserType;
}) => {
try {
const data = await request(`/api/users/${id}`, {
method: 'PUT',
data: values,
});
message.success('编辑成功');
return data;
} catch (error) {
console.log(error);
}
};
// 添加用户
export const addRecord = async ({ values }: { values: SingleUserType }) => {
try {
const data = await request('/api/users', {
method: 'POST',
data: values,
});
message.success('添加成功');
return data;
} catch (error) {
console.log(error);
}
};
// 用户删除
export const deleteRecord = async ({ id }: { id: number }) => {
try {
const data = await request(`/api/users/${id}`, {
method: 'DELETE',
});
message.success('删除成功');
return data;
} catch (error) {
console.log(error);
}
};
import { Form, Input, Modal } from 'antd';
import React, { useEffect } from 'react';
import { SingleUserType } from '../type.d';
type Props = {
visible: boolean;
handleColse: () => void;
record: SingleUserType | undefined;
onFinish: (values: SingleUserType) => void;
onFinishFailed: (errorInfo: any) => void;
};
const UserModal = (props: Props) => {
const { visible, handleColse, record, onFinish, onFinishFailed } = props;
const [form] = Form.useForm();
useEffect(() => {
if (record === undefined) {
form.resetFields();
} else {
form.setFieldsValue(record);
}
}, [visible]);
const handleOk = () => {
form.submit();
};
return (
<div>
<Modal
title="Basic Modal"
visible={visible}
onCancel={handleColse}
onOk={handleOk}
>
<Form
name="basic"
labelCol={{ span: 5 }}
form={form}
initialValues={{}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
label="Name"
name="name"
rules={[{ required: true, message: 'Please Input your name!' }]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email">
<Input />
</Form.Item>
<Form.Item label="Create Time" name="create_time">
<Input />
</Form.Item>
<Form.Item label="Status" name="status">
<Input />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default UserModal;
export interface SingleUserType {
id: number;
name: string;
create_time: string;
update_time: string;
status: number;
}
export interface UserModelState {
data?: SingleUserType[];
meta?: {
total: number;
per_page: number;
page: number;
};
}
Pro-table
组件
使用 - 安装
yarn add @ant-design/pro-table"@2.2.7 moment
import React, { FC, useRef } from 'react';
import { Button, Popconfirm, Space, Table, Pagination, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { connect, Dispatch } from 'dva';
import { Loading } from '@@/plugin-dva/connect';
import ProTable, { ProColumns, TableDropdown } from '@ant-design/pro-table';
import UserModal from './components/UserModal';
import { SingleUserType, UserModelState } from './type';
import { getRemoteList, editRecord, addRecord } from './service';
// 可以使用 hook 来取代connect
// import { useDispatch, useSelector } from 'react-redux';
interface ActionType {
reload: () => void;
fetchMore: () => void;
reset: () => void;
}
interface UserPageProps {
pros: UserModelState;
dispatch: Dispatch;
useListLoading: boolean;
}
const ProsPageList: FC<UserPageProps> = ({
pros,
dispatch,
useListLoading,
}) => {
// const { data } = useSelector((state: any) => state.pros);
// const dispatch = useDispatch();
const [modalVisible, setModalVisible] = React.useState(false);
const [record, setRecord] = React.useState<SingleUserType | undefined>(
undefined,
);
const [confirmLoading, setConfirmLoading] = React.useState(false);
// const ref = useRef<ActionType>();
const columns: ProColumns<SingleUserType>[] = [
{
title: 'ID',
dataIndex: 'id',
valueType: 'digit',
key: 'id',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
valueType: 'text',
render: (text: any) => <a>{text}</a>,
},
{
title: 'CreateTime',
dataIndex: 'create_time',
valueType: 'dateTime',
key: 'create_time',
},
{
title: 'Action',
key: 'action',
render: (text: any, record: SingleUserType) => (
<Space size="middle">
<a onClick={() => handleEdit(record)}>Edit</a>
<a>
<Popconfirm
title="是否要删除这条记录?"
onConfirm={() => comform(record)}
>
Delete
</Popconfirm>
</a>
</Space>
),
},
];
// 编辑
const handleEdit = (record: SingleUserType) => {
setModalVisible(true);
setRecord(record);
};
// 关闭弹窗
const handleColse = () => {
setModalVisible(false);
setRecord(undefined);
};
// 编辑提交
const onFinish = async (values: SingleUserType) => {
setConfirmLoading(true);
let id = 0;
if (record) {
id = record.id;
}
let serviceFun;
if (id) {
serviceFun = editRecord;
} else {
serviceFun = addRecord;
}
const result = await serviceFun({ id, values });
if (result) {
setModalVisible(false);
message.success(`${id === 0 ? 'Add' : 'Edit'} Successfully.`);
reloadHandler();
setConfirmLoading(false);
} else {
setConfirmLoading(false);
message.error(`${id === 0 ? 'Add' : 'Edit'} Failed.`);
}
};
const handleAdd = () => {
setModalVisible(true);
setRecord(undefined);
};
// 删除
const comform = async (record: SingleUserType) => {
dispatch({
type: 'pros/del',
payload: {
id: record.id,
},
});
};
// 提交失败
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const handleRequest = async ({
pageSize = 5,
current = 1,
}: {
pageSize: number;
current: number;
}) => {
const pros = await getRemoteList({
page: current,
per_page: pageSize,
});
console.log('pros', pros.data);
return {
data: pros.data,
success: true,
total: pros?.meta?.total,
};
};
const paginationHandler = (page: number, pageSize?: number) => {
dispatch({
type: 'pros/getRemote',
payload: {
page,
per_page: pageSize ? pageSize : pros?.meta?.per_page,
},
});
};
const pageSizeHandler = (current: number, size: number) => {
dispatch({
type: 'pros/getRemote',
payload: {
page: current,
per_page: size,
},
});
};
// 刷新
const reloadHandler = () => {
dispatch({
type: 'pros/getRemote',
payload: {
page: pros?.meta?.page,
per_page: pros?.meta?.per_page,
},
});
};
return (
<div className="list-table">
{/* <ProTable
actionRef={ref}
loading={useListLoading}
columns={columns}
pagination={false}
rowKey="id"
search={false}
request={handleRequest}
/> */}
<ProTable
loading={useListLoading}
columns={columns}
pagination={false}
rowKey="id"
search={false}
dataSource={pros?.data}
options={{
density: true,
fullScreen: true,
reload: () => {
reloadHandler();
},
setting: true,
}}
headerTitle="User List"
toolBarRender={() => [
<Button type="primary" onClick={handleAdd}>
Add
</Button>,
<Button onClick={reloadHandler}>Reload</Button>,
]}
/>
<Pagination
className="list-page"
total={pros?.meta?.total}
onChange={paginationHandler}
onShowSizeChange={pageSizeHandler}
current={pros?.meta?.page}
pageSize={pros?.meta?.per_page}
showSizeChanger
showQuickJumper
showTotal={(total) => `Total ${total} items`}
/>
<UserModal
visible={modalVisible}
handleColse={handleColse}
record={record}
confirmLoading={confirmLoading}
onFinish={(values) => {
onFinish(values);
}}
onFinishFailed={(errorInfo) => {
onFinishFailed(errorInfo);
}}
/>
</div>
);
};
const mapStateToProps = ({ pros, loading }: { pros: []; loading: Loading }) => {
console.log('pros', pros);
return {
pros,
useListLoading: loading.models.pros,
};
};
export default connect(mapStateToProps)(ProsPageList);
import { Effect, Subscription } from 'dva';
import { Reducer } from 'redux';
import { getRemoteList, editRecord, deleteRecord, addRecord } from './service';
import { UserModelState } from './type';
export interface UserModelType {
namespace: 'pros';
state: UserModelState;
effects: {
query: Effect;
getRemote: Effect;
edit: Effect;
del: Effect;
add: Effect;
};
reducers: {
save: Reducer;
getList: Reducer<UserModelState>;
// 启用 immer 之后
// save: ImmerReducer<UserModelState>;
};
subscriptions: { setup: Subscription };
}
const UserModel: UserModelType = {
namespace: 'pros',
state: {
data: [],
meta: {
total: 0,
per_page: 5,
page: 1,
},
},
effects: {
*query({ payload }, { call, put }) {
console.log('query');
},
// 获取远程数据
*getRemote({ payload }, { call, put }): any {
const data = yield call(getRemoteList, payload);
yield put({
type: 'getList',
payload: data,
});
},
// 编辑用户
*edit({ payload }, { call, put, select }): any {
const data = yield call(editRecord, payload);
// 刷新列表
const { page, per_page } = yield select((state: any) => state.users.meta);
// 刷新列表
yield put({
type: 'getRemote',
payload: {
page,
per_page,
},
});
},
//添加用户
*add({ payload }, { call, put, select }): any {
const data = yield call(addRecord, payload);
// 刷新列表
const { page, per_page } = yield select((state: any) => state.users.meta);
// 刷新列表
yield put({
type: 'getRemote',
payload: {
page,
per_page,
},
});
},
// 删除用户
*del({ payload }, { call, put, select }): any {
const data = yield call(deleteRecord, payload);
const { page, per_page } = yield select((state: any) => state.users.meta);
// 刷新列表
yield put({
type: 'getRemote',
payload: {
page,
per_page,
},
});
},
},
reducers: {
getList(state, action) {
return {
...state,
...action.payload,
};
},
save(state, action) {
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
return data;
// return {
// ...state,
// ...action.payload,
// };
},
// 启用 immer 之后
// save(state, action) {
// state.name = action.payload;
// },
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/pros') {
dispatch({
type: 'getRemote',
payload: {
page: 1,
per_page: 5,
},
});
}
});
},
},
};
export default UserModel;
import { SingleUserType } from './type';
import request, { extend } from 'umi-request';
import { message } from 'antd';
const errorHandler = function (error: any) {
if (error.response) {
if (error.response.status > 400) {
message.error(error.data.message ? error.data.message : error.data);
}
console.log(error.response.status);
console.log(error.response.headers);
console.log(error.data);
console.log(error.request);
} else {
console.log(error.message);
message.error('网络异常,请稍后再试');
}
throw error;
};
const extendRequest = extend({ errorHandler });
// 用户列表
export const getRemoteList = async ({
page,
per_page,
}: {
page: number;
per_page: number;
}) => {
try {
const data = await extendRequest(
`/api/users?page=${page}&per_page=${per_page}`,
{
method: 'GET',
},
);
message.success('列表请求成功');
return data;
} catch (error) {
// message.error(error.message);
console.log(error);
}
};
// 用户编辑
export const editRecord = async ({
id,
values,
}: {
id: number;
values: SingleUserType;
}) => {
try {
const data = await request(`/api/users/${id}`, {
method: 'PUT',
data: values,
});
message.success('编辑成功');
console.log('data', data);
return true;
} catch (error) {
console.log(error);
return false;
}
};
// 添加用户
export const addRecord = async ({ values }: { values: SingleUserType }) => {
try {
const data = await request('/api/users', {
method: 'POST',
data: values,
});
message.success('添加成功');
console.log('data', data);
return true;
} catch (error) {
return false;
console.log(error);
}
};
// 用户删除
export const deleteRecord = async ({ id }: { id: number }) => {
try {
const data = await request(`/api/users/${id}`, {
method: 'DELETE',
});
message.success('删除成功');
return data;
} catch (error) {
console.log(error);
}
};
import { DatePicker, Form, Input, Modal, Switch } from 'antd';
import moment from 'moment';
import React, { useEffect } from 'react';
import { SingleUserType } from '../type';
type Props = {
visible: boolean;
handleColse: () => void;
record: SingleUserType | undefined;
onFinish: (values: SingleUserType) => void;
onFinishFailed: (errorInfo: any) => void;
confirmLoading: boolean;
};
const UserModal = (props: Props) => {
const {
visible,
handleColse,
record,
onFinish,
onFinishFailed,
confirmLoading,
} = props;
const [form] = Form.useForm();
useEffect(() => {
if (record === undefined) {
form.resetFields();
} else {
form.setFieldsValue({
...record,
create_time: moment(record.create_time),
status: Boolean(record.status),
});
}
}, [visible]);
const handleOk = () => {
form.submit();
};
return (
<div>
<Modal
title={record ? 'Edit ID: ' + record.id : 'Add'}
visible={visible}
onCancel={handleColse}
onOk={handleOk}
confirmLoading={confirmLoading}
>
<Form
name="basic"
labelCol={{ span: 5 }}
form={form}
initialValues={{}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
label="Name"
name="name"
rules={[{ required: true, message: 'Please Input your name!' }]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email">
<Input />
</Form.Item>
<Form.Item label="Create Time" name="create_time">
<DatePicker showTime />
</Form.Item>
<Form.Item label="Status" name="status" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default UserModal;