react + zarm 实现账单列表类型以及时间条件弹窗封装

Posted 凯小默:树上的男爵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react + zarm 实现账单列表类型以及时间条件弹窗封装相关的知识,希望对你有一定的参考价值。

需要实现的效果

点击类型,出现下面的条件弹窗

点击时间,出现下面的弹窗

实现过程

这里用到 popup 组件 https://zarm.design/#/components/popup

1.封装类型条件组件

新建 components/PopupType,在其内部新建 index.jsxstyle.module.less 内容如下:

import React,  forwardRef, useEffect, useState  from 'react'
import PropTypes from 'prop-types'
import  Popup, Icon  from 'zarm'
import cx from 'classnames'
import  queryTypeList  from './api/index.js'

import s from './style.module.less'

// forwardRef 用于拿到父组件传入的 ref 属性,这样在父组件便能通过 ref 控制子组件。
const PopupType = forwardRef(( onSelect , ref) => 
  const [show, setShow] = useState(false); // 组件的显示和隐藏
  const [active, setActive] = useState('all'); // 激活的 type
  const [expense, setExpense] = useState([]); // 支出类型标签
  const [income, setIncome] = useState([]); // 收入类型标签

  useEffect(async () => 
    // 请求标签接口放在弹窗内,这个弹窗可能会被复用,所以请求如果放在外面,会造成代码冗余。
    const  data  = await queryTypeList();
    console.log(data);
    setExpense(data.filter(i => i.type == 1))
    setIncome(data.filter(i => i.type == 2))
  , [])

  if (ref) 
    ref.current = 
      // 外部可以通过 ref.current.show 来控制组件的显示
      show: () => 
        setShow(true)
      ,
      // 外部可以通过 ref.current.close 来控制组件的显示
      close: () => 
        setShow(false)
      
    
  ;

  // 选择类型回调
  const choseType = (item) => 
    setActive(item.id)
    setShow(false)
    // 父组件传入的 onSelect,为了获取类型
    onSelect(item)
  ;

  return <Popup
    visible=show
    direction="bottom"
    onMaskClick=() => setShow(false)
    destroy=false
    mountContainer=() => document.body
  >
    <div className=s.popupType>
      <div className=s.header>
        请选择类型
        <Icon type="wrong" className=s.cross onClick=() => setShow(false) />
      </div>
      <div className=s.content>
        <div onClick=() => choseType( id: 'all' ) className=cx( [s.all]: true, [s.active]: active == 'all' )>全部类型</div>
        <div className=s.title>支出</div>
        <div className=s.expenseWrap>
          
            expense.map((item, index) => <p key=index onClick=() => choseType(item) className=cx([s.active]: active == item.id) > item.name </p>)
          
        </div>
        <div className=s.title>收入</div>
        <div className=s.incomeWrap>
          
            income.map((item, index) => <p key=index onClick=() => choseType(item) className=cx([s.active]: active == item.id) > item.name </p>)
          
        </div>
      </div>
    </div>
  </Popup>
);

PopupType.propTypes = 
  onSelect: PropTypes.func


export default PopupType;
.popup-type 
    height: 500px;
    background-color: #f5f5f5;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    .header 
      position: sticky;
      top: 0;
      left: 0;
      z-index: 1000;
      width: 100%;
      height: 56px;
      text-align: center;
      font-size: 14px;
      line-height: 56px;
      color: rgba(0, 0, 0, 0.9);
      background-color: #fff;
      .cross 
        position: absolute;
        right: 10px;
        top: 50%;
        font-size: 20px;
        transform: translateY(-50%);
        color: rgba(0, 0, 0, 0.6);
      
    
    .content 
      padding: 20px;
      .all 
        display: inline-block;
        padding: 12px 20px;
        font-size: 16px;
        color: rgba(0, 0, 0, 0.9);
        background-color: #fff;
      
      .title 
        color: rgba(0, 0, 0, 0.9);
        margin: 10px 0;
        font-size: 14px;
      
      .expense-wrap, .income-wrap 
        display: flex;
        justify-content: space-between;
        flex-wrap: wrap;
        p 
          width: calc(~"(100% - 20px) / 3");
          text-align: center;
          padding: 12px 0;
          margin-bottom: 10px;
          background-color: #fff;
          font-size: 16px;
        
      
      .active 
        background-color: #007fff!important;
        color: #fff;
      
    
  

然后新建 components/PopupType/api,在其内部新建 index.js 添加如下:

import  fetchData  from "@/utils/axios.js";

// 获取类型字典列表
export function queryTypeList(data) 
  return fetchData('/api/type/list', 'get', data);

2.封装时间条件组件

新建 components/PopupDate,在其内部新建 index.jsx 代码如下:

import React,  forwardRef, useState  from 'react'
import PropTypes from 'prop-types'
import  Popup, DatePicker   from 'zarm'
import dayjs from 'dayjs' 

const PopupDate = forwardRef(( onSelect, mode = 'date' , ref) => 
  const [show, setShow] = useState(false)
  const [now, setNow] = useState(new Date())

  const choseMonth = (item) => 
    setNow(item)
    setShow(false)
    if (mode == 'month') 
      onSelect(dayjs(item).format('YYYY-MM'))
     else if (mode == 'date') 
      onSelect(dayjs(item).format('YYYY-MM-DD'))
    
  

  if (ref) 
    ref.current = 
      show: () => 
        setShow(true)
      ,
      close: () => 
        setShow(false)
      
    
  ;
  return <Popup
    visible=show
    direction="bottom"
    onMaskClick=() => setShow(false)
    destroy=false
    mountContainer=() => document.body
  >
    <div>
      <DatePicker
        visible=show
        value=now
        mode=mode
        onOk=choseMonth
        onCancel=() => setShow(false)
      />
    </div>
  </Popup>
);

PopupDate.propTypes = 
  mode: PropTypes.string, // 日期模式
  onSelect: PropTypes.func, // 选择后的回调


export default PopupDate;

3.账单列表组件改动

import React,  useState, useEffect, useRef  from 'react'
import  Icon, Pull  from 'zarm'
import dayjs from 'dayjs'
import BillItem from '@/components/BillItem'
import PopupType from '@/components/PopupType'
import PopupDate from '@/components/PopupDate'
import  queryBillList  from './api/index.js'
import  REFRESH_STATE, LOAD_STATE  from '@/utils/index.js' // Pull 组件需要的一些常量

import s from './style.module.less'

const Home = () => 
  const typeRef = useRef(); // 账单类型 ref
  const monthRef = useRef(); // 月份筛选 ref
  const [currentSelect, setCurrentSelect] = useState(); // 当前筛选类型
  const [currentTime, setCurrentTime] = useState(dayjs().format('YYYY-MM')); // 当前筛选时间
  const [totalExpense, setTotalExpense] = useState(0); // 总支出
  const [totalIncome, setTotalIncome] = useState(0); // 总收入
  const [page, setPage] = useState(1); // 分页
  const [dataList, setDataList] = useState([]); // 账单列表
  const [totalPage, setTotalPage] = useState(0); // 分页总数
  const [refreshing, setRefreshing] = useState(REFRESH_STATE.normal); // 下拉刷新状态
  const [loading, setLoading] = useState(LOAD_STATE.normal); // 上拉加载状态

  useEffect(() => 
    getBillList() // 初始化
  , [page, currentSelect, currentTime])

  // 获取账单方法
  const getBillList = async () => 
    const  data  = await queryBillList(
      curPage: page,
      pageSize: 5,
      typeId: currentSelect.id || "all",
      billDate: currentTime
    );
    // 下拉刷新,重制数据
    if (page == 1) 
      setDataList(data.dataList);
     else 
      setDataList(dataList.concat(data.dataList));
    
    setTotalExpense(data.totalExpense);
    setTotalIncome(data.totalIncome);
    setTotalPage(data.pageObj.totalPage);
    // 上滑加载状态
    setLoading(LOAD_STATE.success);
    setRefreshing(REFRESH_STATE.success);
  

  // 请求列表数据
  const refreshData = () => 
    setRefreshing(REFRESH_STATE.loading);
    if (page != 1) 
      setPage(1);
     else 
      getBillList();
    ;
  ;

  const loadData = () => 
    if (page < totalPage) 
      setLoading(LOAD_STATE.loading);
      setPage(page + 1);
    
  

  // 添加账单弹窗
  const toggle = () => 
    typeRef.current && typeRef.current.show()
  ;
  // 选择月份弹窗
  const monthToggle = () => 
    monthRef.current && monthRef.current.show()
  ;

  // 筛选类型
  const select = (item) => 
    setRefreshing(REFRESH_STATE.loading);
    setPage(1);
    setCurrentSelect(item)
  
  // 筛选月份
  const selectMonth = (item) => 
    setRefreshing(REFRESH_STATE.loading);
    setPage(1);
    setCurrentTime(item)
  

  return <div className=s.home>
    <div className=s.header>
      <div className=s.dataWrap>
        <span className=s.expense>总支出:<b>¥  totalExpense </b></span>
        <span className=s.income>总收入:<b>¥  totalIncome </b></span>
      </div>
      <div className=s.typeWrap>
        <div className=s.left onClick=toggle>
          <span className=s.title> currentSelect.name || '全部类型'  <Icon className=s.arrow type="arrow-bottom" /></span>
        </div>
        <div className=s.right onClick=monthToggle>
          <以上是关于react + zarm 实现账单列表类型以及时间条件弹窗封装的主要内容,如果未能解决你的问题,请参考以下文章

react + zarm 实现账单详情页以及编辑删除功能

react + zarm + antV F2 实现账单数据统计饼图效果

react + zarm 实现底部导航栏

react + zarm + react-captcha-code + classnames 实现登录注册页面

react + zarm + rc-form + crypto-js 实现个人中心页面,头像上传,密码重置,登录退出功能

egg.js + react + zarm ui + vite2.0 全栈项目实战:从 0 到 1 实现记账本小册学习笔记合集(完结)