接下来我们开发主体功能. 通过文章内容的增删改查的实现,来帮助大家快速的掌握ant design pro的常规功能开发.
接口
文章列表功能
接口: /lejuAdmin/productArticle/findArticles/{start}/{limit}
功能说明: 这是一个典型的分页查询功能,另外还包含条件查询. 由于后台实现的需求,虽然是查询功能,但接口请求类型为post.
1. 创建页面:
文件: src\pages\content\Article\index.tsx
PageContainer
: 是antd pro的内容容器布局, 由 pro-layout
实现.
import React from 'react';
// # https://procomponents.ant.design/components/page-container
import {PageContainer} from '@ant-design/pro-layout';
const ArticleList: React.FC = ()=>{
return (
<PageContainer>
文章列表页
</PageContainer>
)
}
export default ArticleList;
2. 添加路由配置
文件: config\routes.ts
....
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
component: './Welcome',
},
// 在此添加 内容管理/文章管理路由
//考虑后续还会追加 内容管理/图片管理 等功能
{
path: '/content',
name: 'content',
icon: 'smile',
routes: [
{
path: '/content/articleList',
name: 'articleList',
icon: 'smile',
component: './content/Article'
}
]
},
...
国际化说明: 此时后台可以访问但会报国际化错误, 这里我们用不到国际化,所以取消国际化配置.
于是再次修改路由配置为:
...
{
path: '/welcome',
name: '欢迎',
icon: 'smile',
component: './Welcome',
},
{
path: '/content',
name: '内容管理',
icon: 'smile',
routes: [
{
path: '/content/articleList',
name: '文章列表',
icon: 'smile',
component: './content/Article'
}
]
},
...
3. 携带token
文件: utils\request.ts
// 假设umi-requset也有拦截器 可以把token的获取放到拦截器中
request.interceptors.request.use((url, options) => {
// 定义新的options 拦截原来的配置
const useOptions = {...options};
// 如果有token直接添加
if(getToken()){
useOptions.headers = {
token: getToken() || ''
}
}
return {
url,
options: { ...useOptions, interceptors: true },
};
});
4. 创建接口
文件: src\services\content\article.ts
import request from '@/utils/request';
// 条件查询,可以根据title,author查询
export type ArticleListSearchType = {
start: number;
limit: number;
params?: {
title?: string;
author?: string;
}
}
/**
* 分页条件查询 文章列表
* @param param0
*/
export async function articleList({start, limit, params}: ArticleListSearchType) {
return request(`/lejuAdmin/productArticle/findArticles/${start}/${limit}`, {
method: 'POST',
data: params,
});
}
5. 创建model
文件: src\pages\content\Article\model.ts
import {articleList as articleListApi} from '@/services/content/article';
import {message} from 'antd';
import type {Effect,Reducer} from 'umi';
// 文章类型 参照接口
export type ArticleType = {
id?: string;
title: string;
author: string;
collectCount: number;
content1?: string;
content2?: string;
coverImg?: string;
createTime?: string;
editorType?: number;
isShow?: number;
modifyTime?: string;
summary?: string;
viewCount?: number;
zanCount?: number;
}
export type MType = {
namespace: string;
state: {
articleList: ArticleType[];
articleTotalCount: number;
};
effects: {
findArticleList: Effect
},
reducers: {
setArticleList: Reducer
}
}
const M: MType = {
namespace: 'article',
state: {
articleList: [],
articleTotalCount: 0
},
effects: {
*findArticleList({payload},{call,put}){
// 解构返回结果,判断是否成功
const {success,data,message: errMsg} = yield call(articleListApi,payload);
if(success){
yield put({
type: 'setArticleList',
payload: data
})
}else{
// 失败提示错误
message.error(errMsg);
}
}
},
reducers: {
setArticleList(state, {payload}){
const {total,rows} = payload;
return {
...state,
articleTotalCount: total,
articleList: rows
}
}
}
}
export default M;
6. 页面添加表格
此时我们需要从 ant design 中查找table组件, 参照 基础表格 样式实现:
我们需要文章列表,所以需要关联model :
修改 src\pages\content\Article\index.tsx
:
import React from 'react';
// # https://procomponents.ant.design/components/page-container
import {PageContainer} from '@ant-design/pro-layout';
import type {ArticleType} from './model';
import {Table} from 'antd';
import {connect} from 'umi';
import type {Dispatch} from 'umi';
// 定义model命名空间
const namespace = 'article';
type StateType = {
articleList: ArticleType[];
dispatch: Dispatch
}
type PropsType = {
articleList: ArticleType[];
dispatch: Dispatch;
}
const colums = [
{
title: 'ID',
dataIndex: 'id'
},
{
title: '标题',
dataIndex: 'title'
},
{
title: '作者',
dataIndex: 'author'
},
{
title: '封面',
render: ({coverImg}: ArticleType) => <img src={coverImg} alt="" />,
},
{
title: '阅读量',
dataIndex: 'viewCount'
},
{
title: '创建时间',
dataIndex: 'createTime'
}
]
const ArticleList: React.FC<PropsType> = props =>{
const {articleList, dispatch} = props;
return (
<PageContainer>
{/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
<Table rowKey="id" columns={colums} dataSource={articleList}/>
</PageContainer>
)
}
/**
* 提取需要的属性
* @param param0
*/
const mapStateToProps = (state: StateType) =>{
return {
articleList: state[namespace].articleList,
dispatch: state.dispatch
}
}
// 自定义的state不符合,但是可以合并connect.d.ts中的connectState
type StateType = {
[namespace]: MStateType;
}&ConnectState;
const mapStateToProps = (state: StateType)=>({
articleList: state[namespace].articleList,
totalCount: state[namespace].totalCount
})
export default connect(mapStateToProps)(ArticleList);
7. useEffect useState
useEffect :
在vue项目中,组件初始化的外部数据请求操作, 我们会在 create()
或者 mounted()
中处理,. 在react中也存在这么一个生命周期函数: componentDidMount
, 但是 [useEffect()](https://react.docschina.org/docs/hooks-effect.html)
可以更加方便的处理这种业务逻辑.
提示: 如果你熟悉 React class 的生命周期函数,你可以把
useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。注意: 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。参阅文档,了解更多关于如何处理函数以及数组频繁变化时的措施内容。 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组(
[]
)作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。 如果你传入了一个空数组([]
),effect 内部的 props 和 state 就会一直拥有其初始值。尽管传入[]
作为第二个参数更接近大家更熟悉的componentDidMount
和componentWillUnmount
思维模式,但我们有更好的方式来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用useEffect
,因此会使得额外操作很方便。
useState : useState
就是一个 Hook (等下我们会讲到这是什么意思)。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState
,但是它不会把新的 state 和旧的 state 进行合并。
简单来说: useState 可以创建一个需要被页面及时响应的变量(跟vue在data中声明一个响应式对象一样).
我们需要在页面加载完毕后,发送请求,所以请求的方法应该写在 useEffect中, 而携带的参数是变化, 所以用 useState来创建:
import React,{useEffect,useState} from 'react';
const ArticleList: React.FC<PropsType> = props =>{
const {articleList, dispatch} = props;
const [params,setParams] = useState({});
useEffect(()=>{
dispatch({
type: `${namespace}/findArticleList`,
payload: {
start: 1,
limit: 5,
params
}
})
},[])
return (
<PageContainer>
{/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
<Table rowKey="id" columns={colums} dataSource={articleList}/>
</PageContainer>
)
}
8. 完善分页功能
import React,{useEffect,useState} from 'react';
// # https://procomponents.ant.design/components/page-container
import {PageContainer} from '@ant-design/pro-layout';
import type {ArticleType} from './model';
import {Table} from 'antd';
import {connect} from 'umi';
import type {Dispatch} from 'umi';
import type {ConnectState} from '@/models/connect.d';
// 定义model命名空间
const namespace = 'article';
type StateType = {
articleList: ArticleType[];
totalCount: number;
dispatch: Dispatch;
loading: any;
}
type PropsType = {
articleList: ArticleType[];
articleTotalCount: number;
dispatch: Dispatch;
loading: boolean;
}
const colums = [
{
title: 'ID',
dataIndex: 'id'
},
{
title: '标题',
dataIndex: 'title'
},
{
title: '作者',
dataIndex: 'author'
},
{
title: '封面',
render: (coverImg: string)=>(
<img src={coverImg} alt=""/>
)
},
{
title: '阅读量',
dataIndex: 'viewCount'
},
{
title: '创建时间',
dataIndex: 'createTime'
}
]
const ArticleList: React.FC<PropsType> = props =>{
const {articleList, totalCount, dispatch ,loading} = props;
// 条件查询,可以根据title,author查询
const [params] = useState({});
// 分页起始页肯定是 1
const [current,setCurrent] = useState(1);
// 数据量不大 为了体现分页 暂时控制每页2条数据
const [pageSize,setPageSize] = useState(2);
// 监听current和pageSize的变化重新发送请求
useEffect(()=>{
dispatch({
type: `${namespace}/findArticleList`,
payload: {
start: current,
limit: pageSize,
params
}
})
},[current,pageSize])
return (
<PageContainer>
{/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
<Table loading={loading} rowKey="id" columns={colums} dataSource={articleList}
pagination = {
{
showQuickJumper: true,
current,
pageSize,
total: totalCount,
showSizeChanger:true,
onShowSizeChange(curr,size){
// console.log(v)
setPageSize(size);
},
onChange(v){
setCurrent(v);
}
}
}
/>
</PageContainer>
)
}
// 自定义的state不符合,但是可以合并connect.d.ts中的connectState
type StateType = {
[namespace]: MStateType;
}&ConnectState;
const mapStateToProps = (state: StateType)=>({
articleList: state[namespace].articleList,
totalCount: state[namespace].totalCount,
loading: state.loading.effects['article/findArticleList'] as boolean
})
export default connect(mapStateToProps)(ArticleList);
export default connect(mapStateToProps)(ArticleList);