需要实现的效果
点击类型,出现下面的条件弹窗
点击时间,出现下面的弹窗
实现过程
这里用到 popup 组件 https://zarm.design/#/components/popup
1.封装类型条件组件
新建 components/PopupType
,在其内部新建 index.jsx
和 style.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}> <span className={s.time}>{ currentTime } <Icon className={s.arrow} type="arrow-bottom" /></span> </div> </div> </div> <div className={s.contentWrap}> { dataList.length ? <Pull animationDuration={200} stayTime={400} refresh={{ state: refreshing, handler: refreshData }} load={{ state: loading, distance: 200, handler: loadData }} > { dataList.map((item, index) => <BillItem bill={item} key={index} />) } </Pull> : <div className={s.noData}>暂无账单数据</div> } <PopupType ref={typeRef} onSelect={select} /> <PopupDate ref={monthRef} mode="month" onSelect={selectMonth} /> </div> </div> } export default Home
样式添加了没有数据的情况
.no-data { text-align: center; font-size: 12px; padding: 10px 0; }
4.测试
当前这个月没有数据,暂时如下:
点击时间,选择到2022年的2月份
发现就有数据了:
切换类型到学习:
选中之后: