feat🔫: 添加记录入账功能
This commit is contained in:
parent
ee9eb0e7a6
commit
e7283c41ca
|
@ -12,12 +12,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
.header {
|
.header {
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
flex: 1 1;
|
flex: 1 1;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import styles from "./Home.module.scss"
|
import styles from "./Home.module.scss"
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import {IBill} from "../../model"
|
import {BillType, IBill} from "../../model"
|
||||||
import Bar from "../../components/charts/bar"
|
import Bar from "../../components/charts/bar"
|
||||||
import {useContext, useEffect, useState} from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import {BillContext} from "../../store";
|
import {BillContext} from "../../store";
|
||||||
import {observer} from "mobx-react-lite";
|
import {observer} from "mobx-react-lite";
|
||||||
import Pie from "../../components/charts/pie";
|
import Pie from "../../components/charts/pie";
|
||||||
import {Card, DatePicker} from "antd";
|
import {Card, DatePicker, Radio, Space} from "antd";
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import 'moment/locale/zh-cn';
|
import 'moment/locale/zh-cn';
|
||||||
|
|
||||||
|
@ -37,6 +37,12 @@ const Home = () => {
|
||||||
setMonth(d.getMonth() + 1)
|
setMonth(d.getMonth() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typeOpt = [
|
||||||
|
{label: '支出', value: BillType.consume},
|
||||||
|
{label: '收入', value: BillType.income},
|
||||||
|
];
|
||||||
|
const [billType, setBillType] = useState(BillType.consume)
|
||||||
|
|
||||||
const TotalMoney = () => <Card>
|
const TotalMoney = () => <Card>
|
||||||
{"总金额"}
|
{"总金额"}
|
||||||
¥{billStore.totalMoney}
|
¥{billStore.totalMoney}
|
||||||
|
@ -45,15 +51,24 @@ const Home = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.home}>
|
<div className={styles.home}>
|
||||||
<div className={styles.total}>
|
<div className={styles.total}>
|
||||||
<DatePicker
|
<Space align="start">
|
||||||
picker="month"
|
<DatePicker
|
||||||
value={moment(`${year}-${month}`, 'YYYY-MM')}
|
picker="month"
|
||||||
onChange={changeDate}
|
value={moment(`${year}-${month}`, 'YYYY-MM')}
|
||||||
/>
|
onChange={changeDate}
|
||||||
<TotalMoney/>
|
/>
|
||||||
|
<Radio.Group
|
||||||
|
options={typeOpt}
|
||||||
|
optionType="button"
|
||||||
|
buttonStyle="solid"
|
||||||
|
value={billType}
|
||||||
|
onChange={e => setBillType(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TotalMoney/>
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<Bar data={transformer(billStore.listAllByDate)}/>
|
<Bar data={transformer(billStore.groupByDate(billType))}/>
|
||||||
<Pie data={transformer(billStore.listAllByClass)}/>
|
<Pie data={transformer(billStore.groupByClass(billType))}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,13 @@
|
||||||
import {
|
import {Button, DatePicker, Input, InputNumber, message, Radio, Select, Space, Table, Tag} from "antd";
|
||||||
Button,
|
import {ArrowDownOutlined, CloudUploadOutlined, DeleteOutlined,} from "@ant-design/icons";
|
||||||
DatePicker,
|
import {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
|
||||||
Input,
|
import {BillType, EmptyBill, IBill} from "../../model";
|
||||||
InputNumber,
|
|
||||||
message,
|
|
||||||
Radio,
|
|
||||||
Select,
|
|
||||||
Space, Table
|
|
||||||
} from "antd";
|
|
||||||
import {
|
|
||||||
ArrowDownOutlined,
|
|
||||||
CloudUploadOutlined, DeleteOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import { BillType, EmptyBill, IBill } from "../../model";
|
|
||||||
import moment from "moment/moment";
|
import moment from "moment/moment";
|
||||||
import { BillContext } from "../../store";
|
import {BillContext} from "../../store";
|
||||||
import { observer } from "mobx-react-lite";
|
import {observer} from "mobx-react-lite";
|
||||||
import styles from "./Record.module.scss"
|
import styles from "./Record.module.scss"
|
||||||
import { BaseSelectRef } from "rc-select/lib/BaseSelect";
|
import {BaseSelectRef} from "rc-select/lib/BaseSelect";
|
||||||
import { createBill } from "../../api/bills";
|
import {createBill} from "../../api/bills";
|
||||||
|
|
||||||
|
|
||||||
function Record() {
|
function Record() {
|
||||||
|
@ -60,8 +48,13 @@ function Record() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "金额",
|
title: "金额",
|
||||||
dataIndex: "money",
|
|
||||||
key: "money",
|
key: "money",
|
||||||
|
render: (_: any, record: IBill) => {
|
||||||
|
const isConsume = record.type === BillType.consume
|
||||||
|
const color = isConsume ? "red" : "green"
|
||||||
|
const flag = isConsume ? "-" : "+"
|
||||||
|
return <Tag color={color}>{flag}{record.money}</Tag>
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "备注",
|
title: "备注",
|
||||||
|
@ -76,7 +69,7 @@ function Record() {
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined/>}
|
||||||
onClick={() => setDataSource(datasource.filter(bill => bill !== record))}
|
onClick={() => setDataSource(datasource.filter(bill => bill !== record))}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -86,8 +79,8 @@ function Record() {
|
||||||
const [datasource, setDataSource] = useState<IBill[]>([])
|
const [datasource, setDataSource] = useState<IBill[]>([])
|
||||||
|
|
||||||
const typeOpt = [
|
const typeOpt = [
|
||||||
{ label: '支出', value: BillType.consume },
|
{label: '支出', value: BillType.consume},
|
||||||
{ label: '收入', value: BillType.income },
|
{label: '收入', value: BillType.income},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 提交到表格
|
// 提交到表格
|
||||||
|
@ -100,7 +93,7 @@ function Record() {
|
||||||
bill.money = Number(money)
|
bill.money = Number(money)
|
||||||
bill.options = options
|
bill.options = options
|
||||||
const checkBill = () => {
|
const checkBill = () => {
|
||||||
return bill.cls !== '' && bill.label !== '' && bill.money > 0
|
return bill.cls !== '' && (billType === BillType.income || bill.label !== '') && bill.money > 0
|
||||||
}
|
}
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
setCls("")
|
setCls("")
|
||||||
|
@ -111,7 +104,6 @@ function Record() {
|
||||||
clsRef.current.focus()
|
clsRef.current.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkBill()) {
|
if (checkBill()) {
|
||||||
Reflect.set(bill, "key", Date.now().toString() + Math.random().toString())
|
Reflect.set(bill, "key", Date.now().toString() + Math.random().toString())
|
||||||
setDataSource([bill, ...datasource])
|
setDataSource([bill, ...datasource])
|
||||||
|
@ -128,7 +120,7 @@ function Record() {
|
||||||
const failures = []
|
const failures = []
|
||||||
for (let bill of datasource) {
|
for (let bill of datasource) {
|
||||||
try {
|
try {
|
||||||
const { id } = await createBill(bill)
|
const {id} = await createBill(bill)
|
||||||
if (!id) failures.push(bill)
|
if (!id) failures.push(bill)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failures.push(bill)
|
failures.push(bill)
|
||||||
|
@ -138,12 +130,22 @@ function Record() {
|
||||||
setUploadLoading(false)
|
setUploadLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let classData: string[]
|
||||||
|
switch (billType) {
|
||||||
|
case BillType.consume:
|
||||||
|
classData = Object.keys(cls2label.consume)
|
||||||
|
break
|
||||||
|
case BillType.income:
|
||||||
|
classData = cls2label.income
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.record}>
|
<div className={styles.record}>
|
||||||
<div className={styles.new}>
|
<div className={styles.new}>
|
||||||
<Space align="start">
|
<Space align="start">
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
|
style={{width: 120}}
|
||||||
options={typeOpt}
|
options={typeOpt}
|
||||||
optionType="button"
|
optionType="button"
|
||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
|
@ -151,13 +153,14 @@ function Record() {
|
||||||
onChange={e => setBillType(e.target.value)}
|
onChange={e => setBillType(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
style={{width: 120}}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
value={moment(date, 'YYYY-MM-DD')}
|
value={moment(date, 'YYYY-MM-DD')}
|
||||||
onChange={(_, dateStr) => setDate(dateStr)}
|
onChange={(_, dateStr) => setDate(dateStr)}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
ref={clsRef}
|
ref={clsRef}
|
||||||
style={{ width: 120 }}
|
style={{width: 120}}
|
||||||
showSearch
|
showSearch
|
||||||
placeholder="类别"
|
placeholder="类别"
|
||||||
optionFilterProp="children"
|
optionFilterProp="children"
|
||||||
|
@ -167,31 +170,33 @@ function Record() {
|
||||||
value={cls === "" ? null : cls}
|
value={cls === "" ? null : cls}
|
||||||
onChange={c => {
|
onChange={c => {
|
||||||
setCls(c)
|
setCls(c)
|
||||||
setLabel(cls2label.consume[c][0])
|
if (billType === BillType.consume) setLabel(cls2label.consume[c][0])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.keys(cls2label.consume)
|
{
|
||||||
.map(c => <Select.Option key={c} value={c}>{c}</Select.Option>)
|
classData.map(c => <Select.Option key={c} value={c}>{c}</Select.Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
{billType === BillType.consume && (
|
||||||
style={{ width: 120 }}
|
<Select
|
||||||
showSearch
|
style={{width: 120}}
|
||||||
placeholder="标签"
|
showSearch
|
||||||
optionFilterProp="children"
|
placeholder="标签"
|
||||||
filterOption={(input, option) =>
|
optionFilterProp="children"
|
||||||
(option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
|
filterOption={(input, option) =>
|
||||||
}
|
(option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
|
||||||
value={label === "" ? null : label}
|
}
|
||||||
onChange={setLabel}>
|
value={label === "" ? null : label}
|
||||||
{cls !== "" &&
|
onChange={setLabel}>
|
||||||
cls2label.consume[cls]
|
{cls !== "" &&
|
||||||
.map(la => <Select.Option key={la} value={la}>{la}</Select.Option>)
|
cls2label.consume[cls]
|
||||||
}
|
.map(la => <Select.Option key={la} value={la}>{la}</Select.Option>)
|
||||||
<Select.Option key={"other"} value={"其他"}>{"其他"}</Select.Option>)
|
}
|
||||||
</Select>
|
<Select.Option key={"other"} value={"其他"}>{"其他"}</Select.Option>)
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: 120 }}
|
style={{width: 120}}
|
||||||
placeholder="money"
|
placeholder="money"
|
||||||
prefix="¥"
|
prefix="¥"
|
||||||
value={money}
|
value={money}
|
||||||
|
@ -199,18 +204,17 @@ function Record() {
|
||||||
onKeyDown={e => e.key === "Enter" && submit()}
|
onKeyDown={e => e.key === "Enter" && submit()}
|
||||||
/>
|
/>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
value={options}
|
style={{width: 180}}
|
||||||
onChange={value => setOptions(value.target.value)}
|
|
||||||
rows={1}
|
rows={1}
|
||||||
placeholder="备注"
|
placeholder="备注"
|
||||||
|
value={options}
|
||||||
|
onChange={value => setOptions(value.target.value)}
|
||||||
onKeyDown={e => e.key === "Enter" && submit()}
|
onKeyDown={e => e.key === "Enter" && submit()}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<ArrowDownOutlined />}
|
icon={<ArrowDownOutlined/>}
|
||||||
onKeyUp={e => e.key === "Tab"
|
onKeyUp={e => e.key === "Tab" && clsRef.current!.focus()}
|
||||||
&& clsRef.current!.focus()
|
|
||||||
}
|
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
>
|
>
|
||||||
提交
|
提交
|
||||||
|
@ -224,7 +228,7 @@ function Record() {
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon={<CloudUploadOutlined />}
|
icon={<CloudUploadOutlined/>}
|
||||||
type="primary"
|
type="primary"
|
||||||
loading={uploadLoading}
|
loading={uploadLoading}
|
||||||
onClick={upload}
|
onClick={upload}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {makeAutoObservable, runInAction} from "mobx";
|
import {makeAutoObservable, runInAction} from "mobx";
|
||||||
import {createContext} from "react";
|
import {createContext} from "react";
|
||||||
import {createBill, getBills, getClass} from "../api/bills";
|
import {createBill, getBills, getClass} from "../api/bills";
|
||||||
import {IBill} from "../model";
|
import {BillType, IBill} from "../model";
|
||||||
import * as R from "ramda"
|
import * as R from "ramda"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,20 +20,31 @@ export class Bill {
|
||||||
get bills() {
|
get bills() {
|
||||||
return this._bills
|
return this._bills
|
||||||
}
|
}
|
||||||
|
|
||||||
get cls2label() {
|
get cls2label() {
|
||||||
return this._cls2label
|
return this._cls2label
|
||||||
}
|
}
|
||||||
|
|
||||||
get listAllByDate() {
|
groupByDate(type?: BillType) {
|
||||||
return R.groupBy((bill: IBill) => bill.date)(this._bills)
|
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type)
|
||||||
|
const functions = R.compose(
|
||||||
|
R.groupBy((bill: IBill) => bill.date),
|
||||||
|
classFun,
|
||||||
|
)
|
||||||
|
return functions(this._bills)
|
||||||
}
|
}
|
||||||
|
|
||||||
get listAllByClass() {
|
groupByClass(type?: BillType) {
|
||||||
return R.groupBy((bill: IBill) => bill.cls)(this._bills)
|
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type)
|
||||||
|
const functions = R.compose(
|
||||||
|
R.groupBy((bill: IBill) => bill.cls),
|
||||||
|
classFun,
|
||||||
|
)
|
||||||
|
return functions(this._bills)
|
||||||
}
|
}
|
||||||
|
|
||||||
get listDailyMoney() {
|
get listDailyMoney() {
|
||||||
return this.listAllByDate
|
return this.groupByDate
|
||||||
}
|
}
|
||||||
|
|
||||||
get totalMoney() {
|
get totalMoney() {
|
||||||
|
@ -47,7 +58,7 @@ export class Bill {
|
||||||
}
|
}
|
||||||
|
|
||||||
get meanMoneyByDate() {
|
get meanMoneyByDate() {
|
||||||
const days = Reflect.ownKeys(this.listAllByDate).length
|
const days = Reflect.ownKeys(this.groupByDate).length
|
||||||
if (days === 0) return 0
|
if (days === 0) return 0
|
||||||
return this.totalMoney / days
|
return this.totalMoney / days
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user