【React入门实践】结合Ant-Design 从0带你手把手开发包含【列表】和【搜索栏】的简单【用户管理】页面

566 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

写在前面的话: 上节我们实现了简单的是form表单,如下,今天我们来学习最常见的表单组件的使用。一步步向更复杂的页面进发。

本次页面核心知识点:(1)【search表单模块】搜索search、重置功能reset、日期选择器DatePicker、下拉框使用select;(2)【列表展示模块】时间规范化moment、tag标签的使用、省略款的Tooltip使用;

1. 看页面需求

 分析:页面分为两个核心部分:【搜索栏】【列表】

2. 分析接口文档

(1)留言列表接口-主要是在页面初始化时调用。pageNum pageSize为请求参数,非必须。响应参数在list中。

 (2)搜索接口 - 但搜索接口一般可以直接使用列表接口即可

 

 3. 参照UI编写页面代码

(1) 列表部分

使用封装的组件(代码后附),data={listData}表示列表中绑定listData数据,注意dataIndex需要对照接口返回的参数编写

  dispatch,
  feedback: { listData, detailData },
  loading
}: any) {
  const init = useCallback(() => {
    dispatch('feedback/init')
  }, [dispatch])
 
  useEffect(() => {
    init()
  }, [init])
 
  const columns = useMemo(
    () => [
      {
        title: '序号',
        dataIndex: '_id'
      },
      {
        title: '用户名',
        dataIndex: 'username'
      },
      {
        title: '反馈标题',
        dataIndex: 'title'
      },
      {
        title: '反馈内容',
        dataIndex: 'content',
      },
      {
        title: '提交时间',
        dataIndex: 'createdTime',
      },
      {
        title: '状态',
        dataIndex: 'status',
      },
      {
        title: '操作',
        render: (value: any) => (
          <>
            {value.status === 0 && (
              <a type="primary" onClick={() => toggleMessage(value.id)}>
                反馈留言
              </a>
            )}
            {value.status === 1 && (
              <a type="primary" onClick={() => toggleDetail(value.id)}>
                查看
              </a>
            )}
          </>
        )
      }
    ],
    [toggleDetail, toggleMessage]
  )
 
  return (
    <PageHeaderLayout className={'commonList'}>
      <Search init={init} dispatch={dispatch} />
      <Card
        className={styles.table}
        style={{ marginTop: 1 }}
        bordered={false}
        bodyStyle={{ padding: '8px 32px 32px 32px' }}
      >
        <StandardTable
          loading={loading}
          data={listData}
          columns={columns}
          onChange={handleStandardTableChange}
        />
      </Card>
    </PageHeaderLayout>
  )
}

(2)search查询栏部分

  const { validateFields } = form
  const simpleForm = useMemo(() => {
    const { getFieldDecorator, getFieldValue } = form
    const { query } = getLocation()
   
    return (
      <Form
        layout="inline"
        style={{ display: 'flex' }}
        className={styles.commonForm}
      >
        <Form.Item label="">
          {getFieldDecorator('username', {
            initialValue: query.username
          })(<Input placeholder="用户名" />)}
        </Form.Item>
        <Form.Item label="">
          {getFieldDecorator('title', {
            initialValue: query.title
          })(<Input placeholder="标题" />)}
        </Form.Item>
        <Form.Item label="">
          {getFieldDecorator('submitTimeBegin', {
            initialValue: query.startDate || ''
          })(
            <DatePicker
              onChange={change1}
              disabledDate={disabledDate1}
              placeholder="请选择日期"
            />
          )}
        </Form.Item>
        <Form.Item label="">
          {getFieldDecorator('submitTimeEnd', {
            initialValue: query.startDate || ''
          })(
            <DatePicker
              onChange={change2}
              disabledDate={disabledDate2}
              placeholder="请选择日期"
            />
          )}
        </Form.Item>
        <Form.Item>
          {getFieldDecorator('status', {
            initialValue: query.status || ''
          })(
            <Select>
              <Option value={''}> 状态 </Option>
              {feedbackStatus.map((v: any) => (
                <Option key={v.key} value={v.key}>
                  {v.value}
                </Option>
              ))}
            </Select>
          )}
        </Form.Item>
        <Button className={styles.search} onClick={handleSearch} icon="search">
          查询
        </Button>
        <Button
          className={styles.reset}
          style={{ marginLeft: 10 }}
          onClick={handleFormReset}
          icon="undo"
        >
          重置
        </Button>
      </Form>
    )
  }, [form, handleFormReset, handleSearch, time1, time2])
  return (
    <Card bordered={false}>
      <div className={'commonForm'}>{simpleForm}</div>
    </Card>
  )
})

可以看到,在页面将search设置成组件,并在后面掉哦组件。至此已达到页面效果,但是还没有数据

4.初始化页面 - 列表数据显示

在page页面的主函数中写init函数

    dispatch('feedback/init')
  }, [dispatch])
 
  useEffect(() => {
    init()
  }, [init])

在model页面中写详细的接口调用信息

      const { query } = getLocation()
      const data = await dispatch({
        type: 'feedback/post',
        params: ['manage/feedback/page', { ...query }]
      })
      dispatch({
        type: 'feedback/setStore',
        params: [{ listData: listMOM1(data) }]
      })
    },

其中我们使用到了ListMOM1组件来帮助页面数据回显,其主要功能是,添加页面的数据编号和实现分页功能,详细的封装代码后附。 其中type: 'feedback/setStore',表示把数据存储在缓存中,可以通过组件传递数据,因此在StandardTable中需要绑定data={listData},从而实现数据初始化显示。

export const listMOM1 = (resData: any) => {
  const {
    content,
    // total,
    totalCount,
    currPage: currPage = 1,
    pageSize: pageSize = 40,
    data,
    list
  } = resData
  const list1 = content || data || list
  //添加序号
  if (list) {
    list.forEach((element: any, index: number) => {
      element._id = index + 1 + (currPage - 1) * pageSize
      element._symbol = element._id
    })
  }
  //分页
  const pagination = {
    total: totalCount,
    pageSize: Number(pageSize) || 40,
    current: Number(currPage)
  }
 
  return {
    list,
    pagination: totalCount && { ...listData.pagination, ...pagination }
  }
}

5.列表数据按需渲染render

可以看到后台返回的数据如下,

但是,(1)【标题和内容】数据过长,我们需要将其展示前5个字符后省略号(比如这样就。。。).(2)对于【日期】数据只需要显示到具体某天,不需要时分秒,(3)对于【状态】数据显示为0,1但是前端也需要整理为‘未回答’‘已回答’

(1)数据截取缩略显示,hover后显示全部---使用Tooltip组件

    title: '反馈内容',
        dataIndex: 'content',
        render: (text: any, record: any, index: any) => {
          if (record.content.length <= 5) {
            return record.content
          } else {
            return (
              <Tooltip title={record.content}>
                {record.content.substring(0, 5) + '...'}
              </Tooltip>
            )
          }
        }
      },

(2)日期截取同理

        title: '提交时间',
        dataIndex: 'createdTime',
        render: (text: any, record: any, index: any) => {
          return record.createdTime.substring(0, 10)
        }
      },

(3)对于接口返回的0,1,2状态数值数据需要分情况渲染

        title: '状态',
        dataIndex: 'status',
        render: (value: any) => (
          <>
            {value == 0 && <Tag color="red">未回答</Tag>}
            {value == 1 && <Tag color="green">已回答</Tag>}
          </>
        )
      },

6. 查询功能实现

    validateFields((err: any, data: any) => {
      // pushPath({
      //   query: data
      // })
      // init()
      const res = { ...data }
      if (data.username !== undefined) {
        res.username = data.username.replace(/\s*/g, '')
      }
      res.pageNum = 1
      dispatch({
        type: 'feedback/search',
        params: [res]
      })
    })
  }, [dispatch, validateFields])

对于按照日期的查询需要做特殊处理。

(1)结束时间需要晚于开始时间

因此DataPicker组件中加入disableddata。

     onChange={change1}
     disabledDate={disabledDate1}
     placeholder="请选择日期"
/>
      return current && current > time2
    }
    function disabledDate2(current: any) {
      return current && current < time1
    }
    function change1(date: any, dateString: any) {
      settime1(date)
      settime11(dateString)
    }
    function change2(date: any, dateString: any) {
      settime2(date)
      settime21(dateString)
    }
    const dataValidate = (rule: any, value: any, callback: any) => {
      if (value && parseInt(value) > parseInt(getFieldValue('priceHigh'))) {
        callback('不能高于最高值')
      } else if (
        value &&
        parseInt(value) < parseInt(getFieldValue('priceLow'))
      ) {
        callback('不能低于最低值')
      } else {
        callback()
      }
    }

(2)提交时间格式处理

const [time21, settime21] = useState('')
const [time1, settime1] = useState(moment().format('YYYY-MM-DD'))
const [time2, settime2] = useState(moment().format('YYYY-MM-DD'))

并在查询函数中添加time时间格式处理

    validateFields((err: any, data: any) => {
      const res = { ...data }
      if (data.username !== undefined) {
        res.username = data.username.replace(/\s*/g, '')
      }
      res.submitTimeBegin = time11
      res.submitTimeEnd = time21
      if (res.submitTimeEnd !== '') {
        res.submitTimeEnd = time21 + ' 23:59:59'
      }
      res.pageNum = 1
      dispatch({
        type: 'feedback/search',
        params: [res]
      })
    })
  }, [dispatch, time11, time21, validateFields])

对于重置函数中也需要添加相应的时间处理

    clearPath()
    init()
    form.resetFields()
    // 重置查询时间
    settime11('')
    settime21('')
  }, [form, init])

至此,完成了本文需要的页面,页面效果如下