接下来我们开发主体功能. 通过文章内容的增删改查的实现,来帮助大家快速的掌握ant design pro的常规功能开发.

接口

image.png

文章列表功能

接口: /lejuAdmin/productArticle/findArticles/{start}/{limit}
功能说明: 这是一个典型的分页查询功能,另外还包含条件查询. 由于后台实现的需求,虽然是查询功能,但接口请求类型为post.

1. 创建页面:

文件: src\pages\content\Article\index.tsx
PageContainer : 是antd pro的内容容器布局, 由 pro-layout 实现.

  1. import React from 'react';
  2. // # https://procomponents.ant.design/components/page-container
  3. import {PageContainer} from '@ant-design/pro-layout';
  4. const ArticleList: React.FC = ()=>{
  5. return (
  6. <PageContainer>
  7. 文章列表页
  8. </PageContainer>
  9. )
  10. }
  11. export default ArticleList;

2. 添加路由配置

文件: config\routes.ts

  1. ....
  2. {
  3. path: '/welcome',
  4. name: 'welcome',
  5. icon: 'smile',
  6. component: './Welcome',
  7. },
  8. // 在此添加 内容管理/文章管理路由
  9. //考虑后续还会追加 内容管理/图片管理 等功能
  10. {
  11. path: '/content',
  12. name: 'content',
  13. icon: 'smile',
  14. routes: [
  15. {
  16. path: '/content/articleList',
  17. name: 'articleList',
  18. icon: 'smile',
  19. component: './content/Article'
  20. }
  21. ]
  22. },
  23. ...

国际化说明: 此时后台可以访问但会报国际化错误, 这里我们用不到国际化,所以取消国际化配置.
image.png
于是再次修改路由配置为:

  1. ...
  2. {
  3. path: '/welcome',
  4. name: '欢迎',
  5. icon: 'smile',
  6. component: './Welcome',
  7. },
  8. {
  9. path: '/content',
  10. name: '内容管理',
  11. icon: 'smile',
  12. routes: [
  13. {
  14. path: '/content/articleList',
  15. name: '文章列表',
  16. icon: 'smile',
  17. component: './content/Article'
  18. }
  19. ]
  20. },
  21. ...

image.png

3. 携带token

文件: utils\request.ts

  1. // 假设umi-requset也有拦截器 可以把token的获取放到拦截器中
  2. request.interceptors.request.use((url, options) => {
  3. // 定义新的options 拦截原来的配置
  4. const useOptions = {...options};
  5. // 如果有token直接添加
  6. if(getToken()){
  7. useOptions.headers = {
  8. token: getToken() || ''
  9. }
  10. }
  11. return {
  12. url,
  13. options: { ...useOptions, interceptors: true },
  14. };
  15. });

4. 创建接口

文件: src\services\content\article.ts

  1. import request from '@/utils/request';
  2. // 条件查询,可以根据title,author查询
  3. export type ArticleListSearchType = {
  4. start: number;
  5. limit: number;
  6. params?: {
  7. title?: string;
  8. author?: string;
  9. }
  10. }
  11. /**
  12. * 分页条件查询 文章列表
  13. * @param param0
  14. */
  15. export async function articleList({start, limit, params}: ArticleListSearchType) {
  16. return request(`/lejuAdmin/productArticle/findArticles/${start}/${limit}`, {
  17. method: 'POST',
  18. data: params,
  19. });
  20. }

5. 创建model

文件: src\pages\content\Article\model.ts

  1. import {articleList as articleListApi} from '@/services/content/article';
  2. import {message} from 'antd';
  3. import type {Effect,Reducer} from 'umi';
  4. // 文章类型 参照接口
  5. export type ArticleType = {
  6. id?: string;
  7. title: string;
  8. author: string;
  9. collectCount: number;
  10. content1?: string;
  11. content2?: string;
  12. coverImg?: string;
  13. createTime?: string;
  14. editorType?: number;
  15. isShow?: number;
  16. modifyTime?: string;
  17. summary?: string;
  18. viewCount?: number;
  19. zanCount?: number;
  20. }
  21. export type MType = {
  22. namespace: string;
  23. state: {
  24. articleList: ArticleType[];
  25. articleTotalCount: number;
  26. };
  27. effects: {
  28. findArticleList: Effect
  29. },
  30. reducers: {
  31. setArticleList: Reducer
  32. }
  33. }
  34. const M: MType = {
  35. namespace: 'article',
  36. state: {
  37. articleList: [],
  38. articleTotalCount: 0
  39. },
  40. effects: {
  41. *findArticleList({payload},{call,put}){
  42. // 解构返回结果,判断是否成功
  43. const {success,data,message: errMsg} = yield call(articleListApi,payload);
  44. if(success){
  45. yield put({
  46. type: 'setArticleList',
  47. payload: data
  48. })
  49. }else{
  50. // 失败提示错误
  51. message.error(errMsg);
  52. }
  53. }
  54. },
  55. reducers: {
  56. setArticleList(state, {payload}){
  57. const {total,rows} = payload;
  58. return {
  59. ...state,
  60. articleTotalCount: total,
  61. articleList: rows
  62. }
  63. }
  64. }
  65. }
  66. export default M;

6. 页面添加表格

此时我们需要从 ant design 中查找table组件, 参照 基础表格 样式实现:
我们需要文章列表,所以需要关联model :
修改 src\pages\content\Article\index.tsx :

  1. import React from 'react';
  2. // # https://procomponents.ant.design/components/page-container
  3. import {PageContainer} from '@ant-design/pro-layout';
  4. import type {ArticleType} from './model';
  5. import {Table} from 'antd';
  6. import {connect} from 'umi';
  7. import type {Dispatch} from 'umi';
  8. // 定义model命名空间
  9. const namespace = 'article';
  10. type StateType = {
  11. articleList: ArticleType[];
  12. dispatch: Dispatch
  13. }
  14. type PropsType = {
  15. articleList: ArticleType[];
  16. dispatch: Dispatch;
  17. }
  18. const colums = [
  19. {
  20. title: 'ID',
  21. dataIndex: 'id'
  22. },
  23. {
  24. title: '标题',
  25. dataIndex: 'title'
  26. },
  27. {
  28. title: '作者',
  29. dataIndex: 'author'
  30. },
  31. {
  32. title: '封面',
  33. render: ({coverImg}: ArticleType) => <img src={coverImg} alt="" />,
  34. },
  35. {
  36. title: '阅读量',
  37. dataIndex: 'viewCount'
  38. },
  39. {
  40. title: '创建时间',
  41. dataIndex: 'createTime'
  42. }
  43. ]
  44. const ArticleList: React.FC<PropsType> = props =>{
  45. const {articleList, dispatch} = props;
  46. return (
  47. <PageContainer>
  48. {/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
  49. <Table rowKey="id" columns={colums} dataSource={articleList}/>
  50. </PageContainer>
  51. )
  52. }
  53. /**
  54. * 提取需要的属性
  55. * @param param0
  56. */
  57. const mapStateToProps = (state: StateType) =>{
  58. return {
  59. articleList: state[namespace].articleList,
  60. dispatch: state.dispatch
  61. }
  62. }
  63. // 自定义的state不符合,但是可以合并connect.d.ts中的connectState
  64. type StateType = {
  65. [namespace]: MStateType;
  66. }&ConnectState;
  67. const mapStateToProps = (state: StateType)=>({
  68. articleList: state[namespace].articleList,
  69. totalCount: state[namespace].totalCount
  70. })
  71. export default connect(mapStateToProps)(ArticleList);

image.png

7. useEffect useState

useEffect :
在vue项目中,组件初始化的外部数据请求操作, 我们会在 create() 或者 mounted() 中处理,. 在react中也存在这么一个生命周期函数: componentDidMount , 但是 [useEffect()](https://react.docschina.org/docs/hooks-effect.html) 可以更加方便的处理这种业务逻辑.

提示: 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

注意: 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。参阅文档,了解更多关于如何处理函数以及数组频繁变化时的措施内容。 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。 如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直拥有其初始值。尽管传入 [] 作为第二个参数更接近大家更熟悉的 componentDidMountcomponentWillUnmount 思维模式,但我们有更好的方式来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。

useState :
useState 就是一个 Hook (等下我们会讲到这是什么意思)。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。
简单来说: useState 可以创建一个需要被页面及时响应的变量(跟vue在data中声明一个响应式对象一样).

我们需要在页面加载完毕后,发送请求,所以请求的方法应该写在 useEffect中, 而携带的参数是变化, 所以用 useState来创建:

  1. import React,{useEffect,useState} from 'react';
  2. const ArticleList: React.FC<PropsType> = props =>{
  3. const {articleList, dispatch} = props;
  4. const [params,setParams] = useState({});
  5. useEffect(()=>{
  6. dispatch({
  7. type: `${namespace}/findArticleList`,
  8. payload: {
  9. start: 1,
  10. limit: 5,
  11. params
  12. }
  13. })
  14. },[])
  15. return (
  16. <PageContainer>
  17. {/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
  18. <Table rowKey="id" columns={colums} dataSource={articleList}/>
  19. </PageContainer>
  20. )
  21. }

image.png

8. 完善分页功能

  1. import React,{useEffect,useState} from 'react';
  2. // # https://procomponents.ant.design/components/page-container
  3. import {PageContainer} from '@ant-design/pro-layout';
  4. import type {ArticleType} from './model';
  5. import {Table} from 'antd';
  6. import {connect} from 'umi';
  7. import type {Dispatch} from 'umi';
  8. import type {ConnectState} from '@/models/connect.d';
  9. // 定义model命名空间
  10. const namespace = 'article';
  11. type StateType = {
  12. articleList: ArticleType[];
  13. totalCount: number;
  14. dispatch: Dispatch;
  15. loading: any;
  16. }
  17. type PropsType = {
  18. articleList: ArticleType[];
  19. articleTotalCount: number;
  20. dispatch: Dispatch;
  21. loading: boolean;
  22. }
  23. const colums = [
  24. {
  25. title: 'ID',
  26. dataIndex: 'id'
  27. },
  28. {
  29. title: '标题',
  30. dataIndex: 'title'
  31. },
  32. {
  33. title: '作者',
  34. dataIndex: 'author'
  35. },
  36. {
  37. title: '封面',
  38. render: (coverImg: string)=>(
  39. <img src={coverImg} alt=""/>
  40. )
  41. },
  42. {
  43. title: '阅读量',
  44. dataIndex: 'viewCount'
  45. },
  46. {
  47. title: '创建时间',
  48. dataIndex: 'createTime'
  49. }
  50. ]
  51. const ArticleList: React.FC<PropsType> = props =>{
  52. const {articleList, totalCount, dispatch ,loading} = props;
  53. // 条件查询,可以根据title,author查询
  54. const [params] = useState({});
  55. // 分页起始页肯定是 1
  56. const [current,setCurrent] = useState(1);
  57. // 数据量不大 为了体现分页 暂时控制每页2条数据
  58. const [pageSize,setPageSize] = useState(2);
  59. // 监听current和pageSize的变化重新发送请求
  60. useEffect(()=>{
  61. dispatch({
  62. type: `${namespace}/findArticleList`,
  63. payload: {
  64. start: current,
  65. limit: pageSize,
  66. params
  67. }
  68. })
  69. },[current,pageSize])
  70. return (
  71. <PageContainer>
  72. {/* rowKey 表格行 key 的取值,可以是字符串或一个函数 标识唯一性,这里我们用每一条数据的id作为key */}
  73. <Table loading={loading} rowKey="id" columns={colums} dataSource={articleList}
  74. pagination = {
  75. {
  76. showQuickJumper: true,
  77. current,
  78. pageSize,
  79. total: totalCount,
  80. showSizeChanger:true,
  81. onShowSizeChange(curr,size){
  82. // console.log(v)
  83. setPageSize(size);
  84. },
  85. onChange(v){
  86. setCurrent(v);
  87. }
  88. }
  89. }
  90. />
  91. </PageContainer>
  92. )
  93. }
  94. // 自定义的state不符合,但是可以合并connect.d.ts中的connectState
  95. type StateType = {
  96. [namespace]: MStateType;
  97. }&ConnectState;
  98. const mapStateToProps = (state: StateType)=>({
  99. articleList: state[namespace].articleList,
  100. totalCount: state[namespace].totalCount,
  101. loading: state.loading.effects['article/findArticleList'] as boolean
  102. })
  103. export default connect(mapStateToProps)(ArticleList);
  104. export default connect(mapStateToProps)(ArticleList);