React【Day4下+5】

环境搭建

使用CRA创建项目,并安装必要依赖,包括下列基础包

  1. Redux状态管理 - @reduxjs/toolkit 、 react-redux
  2. 路由 - react-router-dom
  3. 时间处理 - dayjs
  4. class类名处理 - classnames
  5. 移动端组件库 - antd-mobile
  6. 请求插件 - axios
pnpm i @reduxjs/toolkit react-redux react-router-dom classname dayjs antd-mobile axios

配置别名路径

1. 背景知识

  1. 路径解析配置(webpack),把 @/ 解析为 src/
  2. 路径联想配置(VsCode),VsCode 在输入 @/ 时,自动联想出来对应的 src/下的子级目录

在这里插入图片描述

2. 路径解析配置

配置步骤:

  1. 安装craco
    npm i -D @craco/craco
  2. 项目根目录下创建配置文件
    craco.config.js
  3. 配置文件中添加路径解析配置
  4. 包文件中配置启动和打包命令

在这里插入图片描述在这里插入图片描述

const path = require("path");module.exports = {webpack: {alias: {"@": path.resolve(__dirname, "src"),},},
};

3. 联想路径配置

针对Vscode的配置
配置步骤:

  1. 根目录下新增配置文件 - jsconfig.json
  2. 添加路径提示配置
{"compilerOptions":{"baseUrl":"./","paths":{"@/*":["src/*"]}}
}

在这里插入图片描述

数据Mock实现

在前后端分类的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务功能开发

1. 常见的Mock方式

在这里插入图片描述

2. json-server实现Mock

实现步骤:

  1. 项目中安装json-server
    npm i -D json-server
  2. 准备一个json文件 (素材里获取)
    server / data.json
{"ka": [{"type": "pay","money": -99,"date": "2022-10-24 10:36:42","useFor": "drinks","id": 1},{"type": "pay","money": -88,"date": "2022-10-24 10:37:51","useFor": "longdistance","id": 2},{"type": "income","money": 100,"date": "2022-10-22 00:00:00","useFor": "bonus","id": 3},{"type": "pay","money": -33,"date": "2022-09-24 16:15:41","useFor": "dessert","id": 4},{"type": "pay","money": -56,"date": "2022-10-22T05:37:06.000Z","useFor": "drinks","id": 5},{"type": "pay","money": -888,"date": "2022-10-28T08:21:42.135Z","useFor": "travel","id": 6},{"type": "income","money": 10000,"date": "2023-03-20T06:45:54.004Z","useFor": "salary","id": 7},{"type": "pay","money": -10,"date": "2023-03-22T07:17:12.531Z","useFor": "drinks","id": 8},{"type": "pay","money": -20,"date": "2023-03-22T07:51:20.421Z","useFor": "dessert","id": 9},{"type": "pay","money": -100,"date": "2023-03-22T09:18:12.898Z","useFor": "drinks","id": 17},{"type": "pay","money": -50,"date": "2023-03-23T09:11:23.312Z","useFor": "food","id": 18},{"type": "pay","money": -100,"date": "2023-04-04T03:03:15.617Z","useFor": "drinks","id": 19},{"type": "pay","money": -100,"date": "2023-04-02T16:00:00.000Z","useFor": "food","id": 20},{"type": "income","money": 10000,"date": "2023-02-28T16:00:00.000Z","useFor": "salary","id": 21}]
}
  1. 添加启动命令
"serve": "json-serve ./serve/data.json --port 8888"
  1. 访问接口进行测试
    http://localhost:8888/ka

整体路由设计

在这里插入图片描述

俩个一级路由 (Layout / new)
俩个二级路由 (Layout - mouth/year)

步骤:

  1. Views 里面写简单的页面
const Month = () => {return <div>Month</div>;
};export default Month;
  1. router / index.js
import Layout from "@/views/Layout";
import Month from "@/views/Month";
import New from "@/views/New";
import Year from "@/views/Year";
import { createBrowserRouter } from "react-router-dom";const router = createBrowserRouter([{path: "/",element: <Layout />,children: [{path: "month",element: <Month />,},{path: "year",element: <Year />,},],},{path: "/new",element: <New />,},
]);export default router;
  1. 注入
    根目录 index.js
    配置路由的时候App可以不要了
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
// import App from "./App";
import router from "./router";
import { RouterProvider } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<RouterProvider router={router} />);
  1. 到这一步之前 2级路由 不会显示,因为还没配置2级路由出口!!!!

在year 和month的爹添加二级路由出口

import { Outlet } from "react-router-dom";const Layout = () => {return (<div>layout<Outlet /></div>);
};export default Layout;

antD主题定制

1. 定制方案

在这里插入图片描述如果是全局就是div里面的Button和外边的Button都会生效;如果是局部的话,只有div里面的button才会变

2. 实现方式

  1. 创建css 文件
    src / theme.css

/* 全局定制 */
:root:root {--adm-color-primary: skyblue;
}/* 局部定制 */
.purple {--adm-color-primary: purple;
}
  1. 导入 入口文件 index.js
// 导入定制主题
import "./theme.css";
  1. 使用
import { Outlet } from "react-router-dom";
import { Button } from "antd-mobile";
const Layout = () => {return (<div>layout{/* 注意这里的不是普通的button */}<Button color="primary">ceshi</Button><div className="purple"><Button color="primary">jvbu</Button></div><Outlet /></div>);
};export default Layout;

3. 记账本主题色

:root:root {--adm-color-primary: rgb(105, 174, 120);
}

Redux管理账目列表

在这里插入图片描述

// 账单列表相关storeimport { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'const billStore = createSlice({name: 'bill',// 数据状态stateinitialState: {billList: []},reducers: {// 同步修改方法setBillList (state, action) {state.billList = action.payload}}
})// 解构actionCreater函数
const { setBillList } = billStore.actions
// 编写异步
const getBillList = () => {return async (dispatch) => {// 编写异步请求const res = await axios.get('http://localhost:8888/ka')// 触发同步reducerdispatch(setBillList(res.data))}
}export { getBillList }
// 导出reducer
const reducer = billStore.reducerexport default reducer
// 组合子模块 导出store实例import { configureStore } from '@reduxjs/toolkit'
import billReducer from './modules/billStore'const store = configureStore({reducer: {bill: billReducer}
})export default store

入口文件 index.js

import router from './router'
import { Provider } from 'react-redux'const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Provider store={store}><RouterProvider router={router} /></Provider>
)

TabBar功能实现

在这里插入图片描述

1. 静态布局实现

配套静态模版和样式文件

import { TabBar } from "antd-mobile"
import { useEffect } from "react"
import { Outlet } from "react-router-dom"
import { useDispatch } from 'react-redux'
import { getBillList } from "@/store/modules/billStore"
import './index.scss'
import {BillOutline,CalculatorOutline,AddCircleOutline
} from 'antd-mobile-icons'const tabs = [{key: '/month',title: '月度账单',icon: <BillOutline />,},{key: '/new',title: '记账',icon: <AddCircleOutline />,},{key: '/year',title: '年度账单',icon: <CalculatorOutline />,},
]const Layout = () => {const dispatch = useDispatch()useEffect(() => {dispatch(getBillList())}, [dispatch])return (<div className="layout"><div className="container"><Outlet /></div><div className="footer"><TabBar>{tabs.map(item => (<TabBar.Item key={item.key} icon={item.icon} title={item.title} />))}</TabBar></div></div>)
}export default Layout
.layout {.container {position: fixed;top: 0;bottom: 50px;}.footer {position: fixed;bottom: 0;width: 100%;}
}

下载sass
npm i -D sass

2. 切换路由实现

监听change事件,在事件回调中调用路由跳转方法

 // 切换菜单跳转路由const navigate = useNavigate()const swithRoute = (path) => {console.log(path)navigate(path)}return (<div className="layout"><div className="footer"><TabBar onChange={swithRoute}>{/* 省略... */}</TabBar></div></div>)

月度账单-统计区域

在这里插入图片描述

1. 准备静态结构

views / Month / index.js

import { NavBar, DatePicker } from 'antd-mobile'
import './index.scss'const Month = () => {return (<div className="monthlyBill"><NavBar className="nav" backArrow={false}>月度收支</NavBar><div className="content"><div className="header">{/* 时间切换区域 */}<div className="date"><span className="text">2023 | 3月账单</span><span className='arrow expand'></span></div>{/* 统计区域 */}<div className='twoLineOverview'><div className="item"><span className="money">{100}</span><span className="type">支出</span></div><div className="item"><span className="money">{200}</span><span className="type">收入</span></div><div className="item"><span className="money">{200}</span><span className="type">结余</span></div></div>{/* 时间选择器 */}<DatePickerclassName="kaDate"title="记账日期"precision="month"visible={false}max={new Date()}/></div></div></div >)
}export default Month
.monthlyBill {--ka-text-color: #191d26;height: 100%;background: linear-gradient(180deg, #ffffff, #f5f5f5 100%);background-size: 100% 240px;background-repeat: no-repeat;background-color: rgba(245, 245, 245, 0.9);color: var(--ka-text-color);.nav {--adm-font-size-10: 16px;color: #121826;background-color: transparent;.adm-nav-bar-back-arrow {font-size: 20px;}}.content {height: 573px;padding: 0 10px;overflow-y: scroll;-ms-overflow-style: none; /* Internet Explorer 10+ */scrollbar-width: none; /* Firefox */&::-webkit-scrollbar {display: none; /* Safari and Chrome */}> .header {height: 135px;padding: 20px 20px 0px 18.5px;margin-bottom: 10px;background-image: url(https://zqran.gitee.io/images/ka/month-bg.png);background-size: 100% 100%;.date {display: flex;align-items: center;margin-bottom: 25px;font-size: 16px;.arrow {display: inline-block;width: 7px;height: 7px;margin-top: -3px;margin-left: 9px;border-top: 2px solid #121826;border-left: 2px solid #121826;transform: rotate(225deg);transform-origin: center;transition: all 0.3s;}.arrow.expand {transform: translate(0, 2px) rotate(45deg);}}}}.twoLineOverview {display: flex;justify-content: space-between;width: 250px;.item {display: flex;flex-direction: column;.money {height: 24px;line-height: 24px;margin-bottom: 5px;font-size: 18px;}.type {height: 14px;line-height: 14px;font-size: 12px;}}}
}

2. 点击切换时间选择框

在这里插入图片描述

实现思路:

  1. 准备一个状态数据
  2. 点击切换状态
  3. 根据状态控制弹框打开关闭以及箭头样式
import { NavBar, DatePicker } from 'antd-mobile'
import './index.scss'
import {  useState } from "react"
import classNames from "classnames"const Month = () => {// 控制时间选择器打开关闭const [dateVisible, setDateVisible] = useState(false)// 时间选择框确实事件const dateConfirm = (date) => {// 关闭弹框setDateVisible(false)}return (<div className="monthlyBill"><NavBar className="nav" backArrow={false}>月度收支</NavBar><div className="content"><div className="header">{/* 时间切换区域 */}<div className="date" onClick={() => setDateVisible(true)}>{/* 省略.. */}<span className={classNames('arrow', dateVisible && 'expand')}></span></div>{/* 统计区域 */}{/* 时间选择器 */}<DatePickerclassName="kaDate"title="记账日期"precision="month"visible={dateVisible}max={new Date()}onConfirm={dateConfirm}/></div></div></div >)
}export default Month

onClose是点击蒙层关闭

3. 切换时间显示

在这里插入图片描述

实现思路:

  1. 以当前时间作为默认值
  2. 在时间切换时完成时间修改

用dayjs实现格式化

import dayjs from "dayjs"const [currentMonth, setCurrentMonth] = useState(() => {return dayjs().format('YYYY-MM')
})const dateConfirm = (date) => {setDateVisible(false)const month = dayjs(date).format('YYYY-MM')setCurrentMonth(month)
}

4. 统计功能实现

实现思路:

  1. 按月分组
  2. 根据获取到的时间作为key取当月的账单数组
  3. 根据当月的账单数组计算支出、收入、总计

在这里插入图片描述

1. 按月分组

安装lodash
npm i lodash
使用

import _ from "lodash";
_.groupBy(billList,(item)=>item.data);
  const billList = useSelector((state) => state.bill.billList);const groupList = useMemo(() => {return _.groupBy(billList, (item) => dayjs(item.date).format("YYYY-MM"));}, [billList]);console.log("groupList", groupList);

2. 根据获取到的时间作为key取当月的账单数组

在这里插入图片描述

3. 根据当月的账单数组计算支出、收入、总计

  const [selectedMonthList, setelectedMonthList] = useState([]);const monthResult = useMemo(() => {console.log("monthResult里面的selectedMonthList", selectedMonthList);let income = selectedMonthList.filter((item) => item.type === "income").reduce((pre, cur) => pre + cur.money, 0);let pay = selectedMonthList.filter((item) => item.type === "pay").reduce((pre, cur) => pre + cur.money, 0);console.log(income);console.log(pay);return {pay,income,total: pay + income,};}, [selectedMonthList]);

4. 初始化时渲染统计数据(一打开页面就计算好当前月份的账单

在这里插入图片描述

useEffect(() => {const nowDate = dayjs().format("YYYY-MM");// 边界值控制// 因为是获取数据是异步的,所以  groupList[nowDate] 有可能是空的if (groupList[nowDate]) {setelectedMonthList(groupList[nowDate]);}}, [groupList]);
// 按月分组
// 注意这里的写法  state.bill.billList
const billList = useSelector(state => state.bill.billList)
const monthGroup = useMemo(() => {return _.groupBy(billList, item => dayjs(item.date).format('YYYY-MM'))
}, [billList])// 根据获取到的时间作为key取当月的账单数组
const dateConfirm = (date) => {const monthKey = dayjs(date).format('YYYY-MM')setMonthList(monthGroup[monthKey])
}// 计算统计
const overview = useMemo(() => {const income = currentMonthList.filter(item => item.type === 'income').reduce((a, c) => a + c.money, 0)const pay = currentMonthList.filter(item => item.type === 'pay').reduce((a, c) => a + c.money, 0)return {income,pay,total: income + pay}
}, [currentMonthList])

5. 完整代码

import { useSelector } from "react-redux"
import { NavBar, DatePicker } from 'antd-mobile'
import './index.scss'
import _ from 'lodash'
import dayjs from "dayjs"
import { useMemo, useState } from "react"
import { useEffect } from "react"
import classNames from "classnames"const Month = () => {// 按月分组const billList = useSelector(state => state.bill.billList)const monthGroup = useMemo(() => {return _.groupBy(billList, item => dayjs(item.date).format('YYYY-MM'))}, [billList])// 控制时间选择器打开关闭const [dateVisible, setDateVisible] = useState(false)const [currentMonthList, setMonthList] = useState([])const [currentMonth, setCurrentMonth] = useState(() => {return dayjs().format('YYYY-MM')})const dateConfirm = (date) => {setDateVisible(false)const monthKey = dayjs(date).format('YYYY-MM')setCurrentMonth(monthKey)setMonthList(monthGroup[monthKey])}// 首次加载useEffect(() => {const list = monthGroup[dayjs().format('YYYY-MM')]if(list){setMonthList(list)}}, [monthGroup])// 计算统计const overview = useMemo(() => {if (!currentMonthList) return { income: 0, pay: 0, total: 0 }const income = currentMonthList.filter(item => item.type === 'income').reduce((a, c) => a + c.money, 0)const pay = currentMonthList.filter(item => item.type === 'pay').reduce((a, c) => a + c.money, 0)return {income,pay,total: income + pay}}, [currentMonthList])return (<div className="monthlyBill"><NavBar className="nav" backArrow={false}>月度收支</NavBar><div className="content"><div className="header">{/* 时间切换区域 */}<div className="date" onClick={() => setDateVisible(true)}><span className="text">{currentMonth} 账单</span><span className={classNames('arrow', dateVisible && 'expand')}></span></div>{/* 统计区域 */}<div className='twoLineOverview'><div className="item"><span className="money">{overview.pay.toFixed(2)}</span><span className="type">支出</span></div><div className="item"><span className="money">{overview.income.toFixed(2)}</span><span className="type">收入</span></div><div className="item"><span className="money">{(overview.total).toFixed(2)}</span><span className="type">结余</span></div></div>{/* 时间选择器 */}<DatePickerclassName="kaDate"title="记账日期"precision="month"visible={dateVisible}max={new Date()}onConfirm={dateConfirm}/></div></div></div >)
}export default Month

月度账单-单日统计列表实现

在这里插入图片描述
在这里插入图片描述
views / Month / components / DayBill / index.js

1. 准备组件和配套样式

import classNames from 'classnames'
import './index.scss'const DailyBill = () => {return (<div className={classNames('dailyBill')}><div className="header"><div className="dateIcon"><span className="date">{'03月23日'}</span><span className={classNames('arrow')}></span></div><div className="oneLineOverview"><div className="pay"><span className="type">支出</span><span className="money">{100}</span></div><div className="income"><span className="type">收入</span><span className="money">{200}</span></div><div className="balance"><span className="money">{100}</span><span className="type">结余</span></div></div></div></div>)
}
export default DailyBill

配套样式

.dailyBill {margin-bottom: 10px;border-radius: 10px;background: #ffffff;.header {--ka-text-color: #888c98;padding: 15px 15px 10px 15px;.dateIcon {display: flex;justify-content: space-between;align-items: center;height: 21px;margin-bottom: 9px;.arrow {display: inline-block;width: 5px;height: 5px;margin-top: -3px;margin-left: 9px;border-top: 2px solid #888c98;border-left: 2px solid #888c98;transform: rotate(225deg);transform-origin: center;transition: all 0.3s;}.arrow.expand {transform: translate(0, 2px) rotate(45deg);}.date {font-size: 14px;}}}.oneLineOverview {display: flex;justify-content: space-between;.pay {flex: 1;.type {font-size: 10px;margin-right: 2.5px;color: #e56a77;}.money {color: var(--ka-text-color);font-size: 13px;}}.income {flex: 1;.type {font-size: 10px;margin-right: 2.5px;color: #4f827c;}.money {color: var(--ka-text-color);font-size: 13px;}}.balance {flex: 1;margin-bottom: 5px;text-align: right;.money {line-height: 17px;margin-right: 6px;font-size: 17px;}.type {font-size: 10px;color: var(--ka-text-color);}}}.billList {padding: 15px 10px 15px 15px;border-top: 1px solid #ececec;.bill {display: flex;justify-content: space-between;align-items: center;height: 43px;margin-bottom: 15px;&:last-child {margin-bottom: 0;}.icon {margin-right: 10px;font-size: 25px;}.detail {flex: 1;padding: 4px 0;.billType {display: flex;align-items: center;height: 17px;line-height: 17px;font-size: 14px;padding-left: 4px;}}.money {font-size: 17px;&.pay {color: #ff917b;}&.income {color: #4f827c;}}}}
}
.dailyBill.expand {.header {border-bottom: 1px solid #ececec;}.billList {display: block;}
}

2. 按日分组账单数据

在这里插入图片描述

// 把当前月按日分组账单数据const dayGroup = useMemo(() => {const group = _.groupBy(currentMonthList, (item) => dayjs(item.date).format('YYYY-MM-DD'))return {dayKeys: Object.keys(group),group}}, [currentMonthList])console.log(dayGroup)

3. 遍历日账单组件并传入参数

 {/* 日账单 */}
{dayGroup.dayKeys.map(dayKey => (<DailyBill key={dayKey} date={dayKey} billList={dayGroup.group[dayKey]} />
))}

4. 接收数据计算统计渲染页面

const DailyBill = ({ date, billList }) => {const dayResult = useMemo(() => {// 支出  /  收入  / 结余const pay = billList.filter(item => item.type === 'pay').reduce((a, c) => a + c.money, 0)const income = billList.filter(item => item.type === 'income').reduce((a, c) => a + c.money, 0)return {pay,income,total: pay + income}}, [billList])return (<div className={classNames('dailyBill')}><div className="header"><div className="dateIcon"><span className="date">{date}</span></div><div className="oneLineOverview"><div className="pay"><span className="type">支出</span><span className="money">{dayResult.pay.toFixed(2)}</span></div><div className="income"><span className="type">收入</span><span className="money">{dayResult.income.toFixed(2)}</span></div><div className="balance"><span className="money">{dayResult.total.toFixed(2)}</span><span className="type">结余</span></div></div></div></div>)
}export default DailyBill

月度账单-单日账单列表展示

在这里插入图片描述
在这里插入图片描述

1. 渲染基础列表

{/* 单日列表 */}
<div className="billList">{billList.map(item => {return (<div className="bill" key={item.id}><div className="detail"><div className="billType">{item.useFor}</div></div><div className={classNames('money', item.type)}>{item.money.toFixed(2)}</div></div>)})}
</div>

2. 适配Type

1-准备静态数据

src / contants / index.js

export const billListData = {pay: [{type: 'foods',name: '餐饮',list: [{ type: 'food', name: '餐费' },{ type: 'drinks', name: '酒水饮料' },{ type: 'dessert', name: '甜品零食' },],},{type: 'taxi',name: '出行交通',list: [{ type: 'taxi', name: '打车租车' },{ type: 'longdistance', name: '旅行票费' },],},{type: 'recreation',name: '休闲娱乐',list: [{ type: 'bodybuilding', name: '运动健身' },{ type: 'game', name: '休闲玩乐' },{ type: 'audio', name: '媒体影音' },{ type: 'travel', name: '旅游度假' },],},{type: 'daily',name: '日常支出',list: [{ type: 'clothes', name: '衣服裤子' },{ type: 'bag', name: '鞋帽包包' },{ type: 'book', name: '知识学习' },{ type: 'promote', name: '能力提升' },{ type: 'home', name: '家装布置' },],},{type: 'other',name: '其他支出',list: [{ type: 'community', name: '社区缴费' }],},],income: [{type: 'professional',name: '其他支出',list: [{ type: 'salary', name: '工资' },{ type: 'overtimepay', name: '加班' },{ type: 'bonus', name: '奖金' },],},{type: 'other',name: '其他收入',list: [{ type: 'financial', name: '理财收入' },{ type: 'cashgift', name: '礼金收入' },],},],
}// 目的是将 billListData 中的数据转换为一个对象 billTypeToName,然后调用的时候就通过属性名来得到属性值
export const billTypeToName = Object.keys(billListData).reduce((prev, key) => {billListData[key].forEach(bill => {bill.list.forEach(item => {prev[item.type] = item.name})})return prev
}, {})

2-适配type
导入

import { billTypeToName } from "@/contants";
 <div className="billType">{billTypeToName[item.useFor]}</div>

月度账单-切换打开关闭

image.png

在这里插入图片描述在这里插入图片描述

// 声明状态
const [visible, setVisible] = useState(false)// 控制箭头<span className={classNames('arrow', !visible && 'expand')} onClick={() => setVisible(!visible)}></span>// 控制列表显示
<div className="billList" style={{ display: !visible && 'none' }}></div>

月度账单-Icon组件封装

在这里插入图片描述src / components / Icon /index.js

1. 准备静态结构

const Icon = () => {return (<imgsrc={`https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/reactbase/ka/food.svg`}alt="icon"style={{width: 20,height: 20,}}/>)
}export default Icon

2. 设计参数

传递参数:

  <Icon type={item.useFor}></Icon>
const BASE_URL = 'https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/reactbase/ka/'const Icon = ({ type }) => {return (<imgsrc={`${BASE_URL + type}.svg`}alt="icon"style={{width: 20,height: 20,}}/>)
}export default Icon

3. 使用组件

<div className="billList" style={{ display: visible ? 'block' : 'none' }}>{billList.map(item => {return (<div className="bill" key={item.id}><Icon type={item.useFor} /></div>)})}</div>

记账功能

在这里插入图片描述

记账 - 结构渲染

views/ New / index.js

import { Button, DatePicker, Input, NavBar } from 'antd-mobile'
import Icon from '@/components/Icon'
import './index.scss'
import classNames from 'classnames'
import { billListData } from '@/contants'
import { useNavigate } from 'react-router-dom'const New = () => {const navigate = useNavigate()return (<div className="keepAccounts"><NavBar className="nav" onBack={() => navigate(-1)}>记一笔</NavBar><div className="header"><div className="kaType"><Buttonshape="rounded"className={classNames('selected')}>支出</Button><ButtonclassName={classNames('')}shape="rounded">收入</Button></div><div className="kaFormWrapper"><div className="kaForm"><div className="date"><Icon type="calendar" className="icon" /><span className="text">{'今天'}</span><DatePickerclassName="kaDate"title="记账日期"max={new Date()}/></div><div className="kaInput"><InputclassName="input"placeholder="0.00"type="number"/><span className="iconYuan">¥</span></div></div></div></div><div className="kaTypeList">{billListData['pay'].map(item => {return (<div className="kaType" key={item.type}><div className="title">{item.name}</div><div className="list">{item.list.map(item => {return (<divclassName={classNames('item','')}key={item.type}><div className="icon"><Icon type={item.type} /></div><div className="text">{item.name}</div></div>)})}</div></div>)})}</div><div className="btns"><Button className="btn save">保 存</Button></div></div>)
}export default New

配套样式

.keepAccounts {--ka-bg-color: #daf2e1;--ka-color: #69ae78;--ka-border-color: #191d26;height: 100%;background-color: var(--ka-bg-color);.nav {--adm-font-size-10: 16px;color: #121826;background-color: transparent;&::after {height: 0;}.adm-nav-bar-back-arrow {font-size: 20px;}}.header {height: 132px;.kaType {padding: 9px 0;text-align: center;.adm-button {--adm-font-size-9: 13px;&:first-child {margin-right: 10px;}}.selected {color: #fff;--background-color: var(--ka-border-color);}}.kaFormWrapper {padding: 10px 22.5px 20px;.kaForm {display: flex;padding: 11px 15px 11px 12px;border: 0.5px solid var(--ka-border-color);border-radius: 9px;background-color: #fff;.date {display: flex;align-items: center;height: 28px;padding: 5.5px 5px;border-radius: 4px;// color: #4f825e;color: var(--ka-color);background-color: var(--ka-bg-color);.icon {margin-right: 6px;font-size: 17px;}.text {font-size: 16px;}}.kaInput {flex: 1;display: flex;align-items: center;.input {flex: 1;margin-right: 10px;--text-align: right;--font-size: 24px;--color: var(--ka-color);--placeholder-color: #d1d1d1;}.iconYuan {font-size: 24px;}}}}}.container {}.kaTypeList {height: 490px;padding: 20px 11px;padding-bottom: 70px;overflow-y: scroll;background: #ffffff;border-radius: 20px 20px 0 0;-ms-overflow-style: none; /* Internet Explorer 10+ */scrollbar-width: none; /* Firefox */&::-webkit-scrollbar {display: none; /* Safari and Chrome */}.kaType {margin-bottom: 25px;font-size: 12px;color: #333;.title {padding-left: 5px;margin-bottom: 5px;font-size: 13px;color: #808080;}.list {display: flex;.item {width: 65px;height: 65px;padding: 9px 0;margin-right: 7px;text-align: center;border: 0.5px solid #fff;&:last-child {margin-right: 0;}.icon {height: 25px;line-height: 25px;margin-bottom: 5px;font-size: 25px;}}.item.selected {border: 0.5px solid var(--ka-border-color);border-radius: 5px;background: var(--ka-bg-color);}}}}.btns {position: fixed;bottom: 15px;width: 100%;text-align: center;.btn {width: 200px;--border-width: 0;--background-color: #fafafa;--text-color: #616161;&:first-child {margin-right: 15px;}}.btn.save {--background-color: var(--ka-bg-color);--text-color: var(--ka-color);}}}

记账 - 支出和收入切换

在这里插入图片描述

const new = ()=>{// 1. 区分账单状态const [billType, setBillType] = useState('income')return (<div className="keepAccounts"><div className="kaType">{/* 2. 点击切换状态 */}<Buttonshape="rounded"className={classNames(billType==='pay'?'selected':'')}onClick={() => setBillType('pay')}>支出</Button><ButtonclassName={classNames(billType==='income'?'selected':'')}onClick={() => setBillType('income')}shape="rounded">收入</Button></div>{/* 2. 适配数据 */}<div className="kaTypeList">{billListData[billType].map(item => {})}</div></div>)
}

记账 - 新增一笔

在这里插入图片描述

1. 组件中收集接口数据

import { useDispatch } from 'react-redux'const New = () => {// 收集金额const [money, setMoney] = useState(0)const moneyChange = (value) => {setMoney(value)}// 收集账单类型const [useFor, setUseFor] = useState('')const dispatch = useDispatch()// 保存账单const saveBill = () => {// 收集表单数据const data = {type: billType,money: billType === 'pay' ? -money : +money,date: new Date(),useFor: useFor}console.log(data)dispatch(addBillList(data))}return (<div className="keepAccounts"><NavBar className="nav" onBack={() => navigate(-1)}>记一笔</NavBar><div className="header"><div className="kaType"><Buttonshape="rounded"className={classNames(billType === 'pay' ? 'selected' : '')}onClick={() => setBillType('pay')}>支出</Button><ButtonclassName={classNames(billType === 'income' ? 'selected' : '')}shape="rounded"onClick={() => setBillType('income')}>收入</Button></div><div className="kaFormWrapper"><div className="kaForm"><div className="date"><Icon type="calendar" className="icon" /><span className="text">{'今天'}</span><DatePickerclassName="kaDate"title="记账日期"max={new Date()}/></div><div className="kaInput"><InputclassName="input"placeholder="0.00"type="number"value={money}onChange={moneyChange}/><span className="iconYuan">¥</span></div></div></div></div><div className="kaTypeList">{/* 数据区域 */}{billListData[billType].map(item => {return (<div className="kaType" key={item.type}><div className="title">{item.name}</div><div className="list">{item.list.map(item => {return (<divclassName={classNames('item','')}key={item.type}onClick={() => setUseFor(item.type)}><div className="icon"><Icon type={item.type} /></div><div className="text">{item.name}</div></div>)})}</div></div>)})}</div><div className="btns"><Button className="btn save" onClick={saveBill}>保 存</Button></div></div>)
}export default New

2. 在Redux中编写异步代码

 // 同步添加账单方法addBill(state,action){state.billList.push(action.payload)}//异步请求
const addBillList = (data) => {return async (dispatch) => {// 传递data数据const res = await axios.post("http://localhost:8888/ka", data);dispatch(addBill(res.data));};
};

3. 点击保存提交action

import { useDispatch } from "react-redux";// 点击按钮保存账单const saveBill = () => {console.log("money", money);const data = {type: billType,money: billType === "pay" ? -1 * money : money,data: new Date(),useFor: type,};console.log("data", data);dispatch(addBillList(data));  // 提交数据};

4. 选中的图标是激活状态

 className={classNames("item",type === item.type && "selected")}key={item.type}

5. 时间选择

6. 如果日期是今天就显示“今天”而不是2024-04-15

 const [selectedDate, setSelectedDate] = useState(new Date());<span className="text">{selectedDate === new Date()? "今天": dayjs(selectedDate).format("YYYY-MM-DD")}</span>

为什么二者不相等
在 JavaScript 中,两个对象即使它们包含相同的属性和值,但它们引用的内存地址不同,因此它们是不相等的。在这种情况下,selectedDate 是一个 Date 对象,new Date() 创建了一个新的 Date 对象,并且 selectedDate 也是一个 Date 对象,尽管它们的值可能相同,但它们引用的内存地址不同,所以它们是不相等的。

要比较两个日期对象的值是否相等,你应该比较它们的时间戳或者转换为字符串后再进行比较,例如:

selectedDate.getTime() === new Date().getTime()

或者:

selectedDate.toISOString() === new Date().toISOString()

这样可以确保比较的是它们的值而不是引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/637891.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为开源自研AI框架昇思MindSpore应用案例:数据处理性能优化

如果你对MindSpore感兴趣&#xff0c;可以关注昇思MindSpore社区 数据是整个深度学习中最重要的一环&#xff0c;因为数据的好坏决定了最终结果的上限&#xff0c;模型的好坏只是去无限逼近这个上限&#xff0c;所以高质量的数据输入&#xff0c;会在整个深度神经网络中起到积极…

内存管理下及模板初阶

嗨喽&#xff0c;今天阿鑫给大家带来内存管理下以及模板初阶的博客&#xff0c;下面让我们开始今天的学习吧&#xff01; 内存管理下及模板初阶 new和delete的实现原理定位new表达式(placement-new)常见面试题泛型编程函数模板类模板 1. new和delete的实现原理 1.1 内置类型…

知道做到 一篇总结学习方法的笔记

元数据 [!abstract] 知道做到&#xff1a;跃迁式学习 书名&#xff1a; 知道做到&#xff1a;跃迁式学习作者&#xff1a; 彼得•霍林斯简介&#xff1a; 学习是改善你的生活环境、成为你想成为的人的关键。科学的方法能加速学习进程&#xff0c;让你事半功倍。技能、信息和能力…

SpringBoot 操作 Redis

导入对应版本的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>修改配置文件中的信息 spring:redis:host: 127.0.0.1port: 8888注意: 我这里 xsh…

SpringAOP从入门到源码分析大全(三)ProxyFactory源码分析

文章目录 系列文档索引五、ProxyFactory源码分析1、案例2、认识TargetSource&#xff08;1&#xff09;何时用到TargetSource&#xff08;2&#xff09;Lazy的原理&#xff08;3&#xff09;应用TargetSource 3、ProxyFactory选择cglib或jdk动态代理原理4、jdk代理获取代理方法…

数码摄影色彩构成,数码相机色彩管理

一、资料描述 本套摄影色彩资料&#xff0c;大小58.54M&#xff0c;共有6个文件。 二、资料目录 《抽象彩色摄影集》.阿瑟.pdf 《色彩构成》.pdf 《色彩学》.星云.扫描版.pdf 《摄影色彩构成》.pdf 《数码相机色彩管理》.pdf 数码摄影进阶之4《色彩篇》.pdf 三、资料下…

随心玩玩(十五)LLM 浅玩一下RWKV模型

写在前面&#xff1a;无聊又来玩玩LLM了&#xff0c;这次玩玩RWKV&#xff0c;因为我看到hugging face上有个roleplay的模型&#xff0c;遂心动。 【烧显存注意&#xff01;】需要16G显存的样子 介绍 &#xff08;随便cv的&#xff0c;见谅&#xff0c;有空单独写一篇博客看看…

Matlab新手快速上手2(粒子群算法)

本文根据一个较为简单的粒子群算法框架详细分析粒子群算法的实现过程&#xff0c;对matlab新手友好&#xff0c;源码在文末给出。 粒子群算法简介 粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种群体智能优化算法&#xff0c;灵感来源于…

websocket 请求头报错 Provisional headers are shown 的解决方法

今日简单总结 websocket 使用过程中遇到的问题&#xff0c;主要从以下三个方面来分享&#xff1a; 1、前端部分 websocket 代码 2、使用 koa.js 实现后端 websocket 服务搭建 3、和后端 java Netty 库对接时遇到连接失败问题 一、前端部分 websocket 代码 <template>…

社会工程渗透测试教程(四)

原文&#xff1a;annas-archive.org/md5/db987a87e1478b8a8617c263c631b477 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十三章&#xff1a;撰写报告 安德鲁梅森&#xff0c;技术总监&#xff0c;RandomStorm 有限公司 本章将介绍向客户提供的主要成果&#xf…

嵌入式物联网实战开发笔记-乐鑫ESP32开发环境ESP-IDF搭建【doc.yotill.com】

乐鑫ESP32入门到精通项目开发参考百例下载&#xff1a; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;4e33 3.1 ESP-IDF 简介 ESP-IDF&#xff08;Espressif IoT Development Framework&#xff09;是乐鑫&#xff08;Espressif Systems&#xff09;为 ESP 系列…

FineBi中创建自定义的图表

FineBi中增加自己的自定义图表组件,比如: 的相关笔记: 1 获取有哪些BI自定义图表组件:http://localhost:8080/webroot/decision/v5/plugin/custom/component/list?_=1713667435473[{"name": "图表DEMO_EK","chartType": "amap_demo&q…