需要实现的交互效果
大致如下图:
实现过程
1.先编写新增按钮
先实现点击新增按钮,调出弹窗的功能。
在 src\container\Home\index.jsx 里面添加下面的代码
import CustomIcon from '@/components/CustomIcon'
import PopupAddBill from '@/components/PopupAddBill'
...
const Home = () => {
...
const addRef = useRef();
const addToggle = () => {
addRef.current && addRef.current.show()
}
...
return <div className={s.home}>
<div className={s.contentWrap}>
<PopupAddBill ref={addRef} onReload={refreshData} />
</div>
...
<div className={s.add} onClick={addToggle}><CustomIcon type='bianji' /></div>
</div>
}
在 src\container\Home\style.module.less 添加样式
给 border 设置的是 1PX,大写的单位,因为这样写的话,postcss-pxtorem 插件就不会将其转化为 rem 单位。
.home {
...
.add {
position: fixed;
bottom: 100px;
right: 30px;
z-index: 1000;
width: 50px;
height: 50px;
border-radius: 50%;
box-shadow: 0 0 10px 0 rgb(0 0 0 / 20%);
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
border: 1PX solid #e9e9e9;
color: #007fff;
}
}
我们就能得到下面的效果
2.添加datatime日期选择类型
在 src\components\PopupDate\index.jsx 添加 datetime 类型
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'))
} else if (mode == 'datetime') {
onSelect(dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
}
}
用于实现下面的效果:
3.实现新增账单弹窗封装
在 components 下新建 PopupAddBill 文件夹,再新建 index.jsx 和 style.module.less ,代码如下:
- 账单类型和账单时间
- 将金额动态化,引入 Zarm 为我们提供的模拟数字键盘组件 Keyboard,这里我们使用 “zarm”: “^2.8.2”,新版本好像有点问题
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { Popup, Icon, Keyboard, Input, Toast } from 'zarm';
import CustomIcon from '@/components/CustomIcon'
import PopupDate from '../PopupDate'
import dayjs from 'dayjs';
import PropTypes from 'prop-types';
import { queryTypeList, addBillData } from './api/index.js'
import { typeMap } from '@/utils';
import cx from 'classnames';
import s from './style.module.less';
const PopupAddBill = forwardRef((props, ref) => {
const dateRef = useRef();
const [show, setShow] = useState(false)
const [date, setDate] = useState(new Date());
const [payType, setPayType] = useState('expense');
const [currentType, setCurrentType] = useState({});
const [amount, setAmount] = useState('');
const [expense, setExpense] = useState([]);
const [income, setIncome] = useState([]);
const [remark, setRemark] = useState('');
const [showRemark, setShowRemark] = useState(false);
if (ref) {
ref.current = {
show: () => {
setShow(true);
},
close: () => {
setShow(false);
}
}
};
useEffect(async () => {
const { data } = await queryTypeList({});
const _expense = data.filter(i => i.type == 1);
const _income = data.filter(i => i.type == 2);
setExpense(_expense);
setIncome(_income);
setCurrentType(_expense[0]);
}, [])
const changeType = (type) => {
setPayType(type);
type == 'expense' ? setCurrentType(expense[0]) : setCurrentType(income[0]);
};
const selectDate = (val) => {
console.log('日期选择回调', val)
setDate(val);
}
const handleMoney = (value) => {
console.log('value', value)
value = String(value)
if(value == 'close') {
setShow(false)
return
}
if (value == 'delete') {
let _amount = amount.slice(0, amount.length - 1)
setAmount(_amount)
return
}
if (value == 'ok') {
addBill()
return
}
if (value == '.' && amount.includes('.')) return
if (value != '.' && amount.includes('.') && amount && amount.split('.')[1].length >= 2) return
setAmount(amount + value)
}
const addBill = async () => {
if (!amount) {
Toast.show('请输入具体金额')
return
}
const params = {
amount: Number(amount).toFixed(2),
type_id: currentType.id,
type_name: currentType.name,
date: dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
pay_type: payType == 'expense' ? 1 : 2,
remark: remark || ''
}
const result = await addBillData(params);
console.log(result);
setAmount('');
setPayType('expense');
setCurrentType(expense[0]);
setDate(new Date());
setRemark('');
Toast.show('添加成功');
setShow(false);
if (props.onReload) props.onReload();
}
return <Popup
visible={show}
direction="bottom"
onMaskClick={() => setShow(false)}
destroy={false}
mountContainer={() => document.body}
>
<div className={s.addWrap}>
{}
<header className={s.header}>
<span className={s.close} onClick={() => setShow(false)}><Icon type="wrong" /></span>
</header>
{}
<div className={s.filter}>
<div className={s.type}>
<span onClick={() => changeType('expense')} className={cx({ [s.expense]: true, [s.active]: payType == 'expense' })}>支出</span>
<span onClick={() => changeType('income')} className={cx({ [s.income]: true, [s.active]: payType == 'income' })}>收入</span>
</div>
<div
className={s.time}
onClick={() => dateRef.current && dateRef.current.show()}
>{dayjs(date).format('YYYY-MM-DD HH:mm')} <Icon className={s.arrow} type="arrow-bottom" /></div>
</div>
<div className={s.money}>
<span className={s.sufix}>¥</span>
<span className={cx(s.amount, s.animation)}>{amount}</span>
</div>
<div className={s.typeWarp}>
<div className={s.typeBody}>
{}
{
(payType == 'expense' ? expense : income).map(item => <div onClick={() => setCurrentType(item)} key={item.id} className={s.typeItem}>
{}
<span className={cx({[s.iconfontWrap]: true, [s.expense]: payType == 'expense', [s.income]: payType == 'income', [s.active]: currentType.id == item.id})}>
<CustomIcon className={s.iconfont} type={typeMap[item.id].icon} />
</span>
<span>{item.name}</span>
</div>)
}
</div>
</div>
<div className={s.remark}>
{
showRemark ? <Input
autoHeight
showLength
maxLength={50}
type="text"
rows={3}
value={remark}
placeholder="请输入备注信息"
onChange={(val) => setRemark(val)}
onBlur={() => setShowRemark(false)}
/> : <span onClick={() => setShowRemark(true)}>{remark || '添加备注'}</span>
}
</div>
<Keyboard type="price" onKeyClick={(value) => handleMoney(value)} />
<PopupDate ref={dateRef} mode="datetime" onSelect={selectDate} />
</div>
</Popup>
})
export default PopupAddBill
.add-wrap {
padding-top: 12px;
background-color: #fff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.header {
padding: 0 16px;
.close {
display: flex;
align-items: center;
justify-content: flex-end;
}
}
.filter {
padding: 12px 24px;
display: flex;
justify-content: space-between;
align-items: center;
.type {
span {
display: inline-block;
background: #f5f5f5;
color: rgba(0, 0, 0, 0.5);
padding: 4px 12px;
font-size: 12px;
border-radius: 24px;
border: 1px solid #f5f5f5;
}
.expense {
margin-right: 6px;
&.active {
background-color: #eafbf6;
border-color: #007fff;
color: #007fff;
}
}
.income {
&.active {
background-color: #fbf8f0;
border-color: rgb(236, 190, 37);
color: rgb(236, 190, 37);
}
}
}
}
.time {
display: flex;
justify-content: center;
align-items: center;
padding: 4px 12px;
background-color: #f0f0f0;
border-radius: 20px;
color: rgba(0, 0, 0, 0.9);
.arrow {
font-size: 12px;
margin-left: 5px;
}
}
.money {
padding-bottom: 12px;
border-bottom: 1px solid #e9e9e9;
margin: 0 24px;
.sufix {
font-size: 36px;
font-weight: bold;
vertical-align: top;
}
.amount {
font-size: 40px;
padding-left: 10px;
}
}
.type-warp {
display: flex;
overflow-x: auto;
margin: 0 24px;
margin-bottom: 20px;
* {
touch-action: pan-x;
}
.type-body {
display: flex;
white-space: nowrap;
.type-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 16px 12px 10px 12px;
.iconfont-wrap {
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
border-radius: 50%;
width: 30px;
height: 30px;
margin-bottom: 5px;
.iconfont {
color: rgba(0, 0, 0, 0.5);
font-size: 20px;
}
}
.expense {
&.active {
background-color: #007fff;
.iconfont {
color: #fff;
}
}
}
.income {
&.active {
background-color: rgb(236, 190, 37);
.iconfont {
color: #fff;
}
}
}
}
}
}
.remark {
padding: 0 24px;
padding-bottom: 12px;
color: #4b67e2;
:global {
.za-input--textarea {
border: 1px solid #e9e9e9;
padding: 10px;
}
}
}
}
4.测试效果
我们先点新增按钮
然后就会弹出新增账单的窗口
我们测试一下支出的(收入可以自己去测试一下)
填写好信息之后,我们点击确定,发现新增支出的账单数据就添加成功了。
另外点击日期时间选择:我改成了精确到分钟
|