u
This commit is contained in:
1 parent
49285f1df2
commit
63b168affc
7 files changed
+284
-324
No files matched your search
@@ -1,22 +1,21 @@
|
||||
import type { MoneyRecordGroup, MoneyRecordItem, QueryMoneyRecordsResult } from '@/types/money/record'
|
||||
import { useRouter } from 'expo-router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'
|
||||
import { Text, TouchableOpacity, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { ADatePicker, LucideIcon } from '@/components/common/ao'
|
||||
import { Fab, FabIcon } from '@/components/ui/fab'
|
||||
import { AddIcon } from '@/components/ui/icon'
|
||||
import { Progress, ProgressFilledTrack } from '@/components/ui/progress'
|
||||
import { deleteMoneyRecordApi, queryMoneyRecordsApi } from '@/request/api/money/record'
|
||||
import { add, sub } from '@/utils/math'
|
||||
import { getCurrentYearMonth, isInMonth } from '@/utils/time'
|
||||
import { useMoneyRecordStore } from '@/store/moneyRecord'
|
||||
import { getCurrentYearMonth } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
import AddMoneyDrawer from './components/AddDrawer'
|
||||
import TransactionsList from './components/TransactionsList'
|
||||
|
||||
export default function Money() {
|
||||
const moneyRecordStore = useMoneyRecordStore()
|
||||
const router = useRouter()
|
||||
const [showAddDrawer, setShowAddDrawer] = useState(false)
|
||||
// 安全高度
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
@@ -25,22 +24,13 @@ export default function Money() {
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
record_time: getCurrentYearMonth(),
|
||||
})
|
||||
// 数据
|
||||
const [moneyRecords, setMoneyRecords] = useState<QueryMoneyRecordsResult>({
|
||||
total_expense: '0',
|
||||
total_income: '0',
|
||||
records: [],
|
||||
})
|
||||
// 更新
|
||||
const [updateItem, setUpdateItem] = useState<MoneyRecordItem | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
queryMoneyRecordsApi({
|
||||
record_time: searchParams.record_time,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
console.log(res.data.records)
|
||||
setMoneyRecords(res.data)
|
||||
moneyRecordStore.setRecords(res.data.records, res.data.total_expense, res.data.total_income, searchParams.record_time)
|
||||
}
|
||||
else {
|
||||
toast.show({
|
||||
@@ -52,232 +42,6 @@ export default function Money() {
|
||||
})
|
||||
}, [searchParams])
|
||||
|
||||
function handleAddMoneyRecord(data: MoneyRecordItem) {
|
||||
const recordTime = data.record_time
|
||||
|
||||
setMoneyRecords((prev) => {
|
||||
// 不在当前月份 → 不添加,直接返回
|
||||
if (!isInMonth(recordTime, searchParams.record_time)) {
|
||||
return prev
|
||||
}
|
||||
|
||||
const next = structuredClone(prev)
|
||||
|
||||
// 查找分组
|
||||
const group = next.records.find(g => g.record_time === recordTime)
|
||||
|
||||
// === 已存在分组 ===
|
||||
if (group) {
|
||||
// 插入到最前面
|
||||
group.records.unshift(data)
|
||||
|
||||
// 更新分组 & 总计
|
||||
if (data.category_type === 1) {
|
||||
// 收入
|
||||
group.income = add(group.income, data.amount)
|
||||
next.total_income = add(next.total_income, data.amount)
|
||||
}
|
||||
else if (data.category_type === 0) {
|
||||
// 支出
|
||||
group.expense = add(group.expense, data.amount)
|
||||
next.total_expense = add(next.total_expense, data.amount)
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
// === 无分组,新建 ===
|
||||
const expense = data.category_type === 0 ? data.amount : '0'
|
||||
const income = data.category_type === 1 ? data.amount : '0'
|
||||
|
||||
const newGroup: MoneyRecordGroup = {
|
||||
record_time: recordTime,
|
||||
income,
|
||||
expense,
|
||||
records: [data],
|
||||
}
|
||||
|
||||
// ---- 按日期(YYYY-MM-DD)倒序插入 ----
|
||||
// 最新日期插入前面,比如:2025-01-05 > 2025-01-03
|
||||
const index = next.records.findIndex(g => g.record_time < recordTime)
|
||||
|
||||
if (index === -1) {
|
||||
next.records.push(newGroup)
|
||||
}
|
||||
else {
|
||||
next.records.splice(index, 0, newGroup)
|
||||
}
|
||||
|
||||
// 更新总计
|
||||
if (data.category_type === 0) {
|
||||
next.total_income = add(next.total_income, data.amount)
|
||||
}
|
||||
else if (data.category_type === 1) {
|
||||
next.total_expense = add(next.total_expense, data.amount)
|
||||
}
|
||||
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdateMoneyRecord(updated: MoneyRecordItem) {
|
||||
setMoneyRecords((prev) => {
|
||||
const next = structuredClone(prev)
|
||||
|
||||
// 找到旧记录
|
||||
const oldGroupIndex = next.records.findIndex(g =>
|
||||
g.records.some(r => r.id === updated.id),
|
||||
)
|
||||
if (oldGroupIndex === -1)
|
||||
return next
|
||||
|
||||
const oldGroup = next.records[oldGroupIndex]
|
||||
const oldRecordIndex = oldGroup.records.findIndex(r => r.id === updated.id)
|
||||
const oldRecord = oldGroup.records[oldRecordIndex]
|
||||
|
||||
const oldAmount = oldRecord.amount
|
||||
const newAmount = updated.amount
|
||||
const oldType = oldRecord.category_type
|
||||
const newType = updated.category_type
|
||||
const oldDate = oldRecord.record_time
|
||||
const newDate = updated.record_time
|
||||
|
||||
// record_time 没变 → 同分组更新
|
||||
if (oldDate === newDate) {
|
||||
oldGroup.records[oldRecordIndex] = updated
|
||||
|
||||
// 调整分组金额
|
||||
if (oldType === 1)
|
||||
oldGroup.income = sub(oldGroup.income, oldAmount)
|
||||
if (oldType === 0)
|
||||
oldGroup.expense = sub(oldGroup.expense, oldAmount)
|
||||
|
||||
if (newType === 1)
|
||||
oldGroup.income = add(oldGroup.income, newAmount)
|
||||
if (newType === 0)
|
||||
oldGroup.expense = add(oldGroup.expense, newAmount)
|
||||
|
||||
// 调整总计
|
||||
if (oldType === 1)
|
||||
next.total_income = sub(next.total_income, oldAmount)
|
||||
if (oldType === 0)
|
||||
next.total_expense = sub(next.total_expense, oldAmount)
|
||||
|
||||
if (newType === 1)
|
||||
next.total_income = add(next.total_income, newAmount)
|
||||
if (newType === 0)
|
||||
next.total_expense = add(next.total_expense, newAmount)
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// 判断是否在当前月份内
|
||||
// -------------------------
|
||||
const currentMonth = searchParams.record_time // 例如 "2025-01"
|
||||
|
||||
const isInCurrentMonth = isInMonth(newDate, currentMonth)
|
||||
|
||||
// 1. 先移除旧分组 & 调整金额(无论是否在当前月份,都要做)
|
||||
if (oldType === 1)
|
||||
oldGroup.income = sub(oldGroup.income, oldAmount)
|
||||
if (oldType === 0)
|
||||
oldGroup.expense = sub(oldGroup.expense, oldAmount)
|
||||
|
||||
if (oldType === 1)
|
||||
next.total_income = sub(next.total_income, oldAmount)
|
||||
if (oldType === 0)
|
||||
next.total_expense = sub(next.total_expense, oldAmount)
|
||||
|
||||
oldGroup.records.splice(oldRecordIndex, 1)
|
||||
if (oldGroup.records.length === 0) {
|
||||
next.records.splice(oldGroupIndex, 1)
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// 不在当前月份 → 不显示,直接返回 next
|
||||
// -------------------------
|
||||
if (!isInCurrentMonth) {
|
||||
return next
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// 在当前月份 → 移动到新分组
|
||||
// -------------------------
|
||||
|
||||
// 找新分组,如果没有就创建
|
||||
let newGroup = next.records.find(g => g.record_time === newDate)
|
||||
if (!newGroup) {
|
||||
newGroup = {
|
||||
record_time: newDate,
|
||||
income: '0.00',
|
||||
expense: '0.00',
|
||||
records: [],
|
||||
}
|
||||
// 按日期倒序插入
|
||||
const insertIndex = next.records.findIndex(g => g.record_time < newDate)
|
||||
if (insertIndex === -1)
|
||||
next.records.push(newGroup)
|
||||
else
|
||||
next.records.splice(insertIndex, 0, newGroup)
|
||||
}
|
||||
|
||||
// 添加到新分组
|
||||
newGroup.records.unshift(updated)
|
||||
|
||||
// 加金额
|
||||
if (newType === 1)
|
||||
newGroup.income = add(newGroup.income, newAmount)
|
||||
if (newType === 0)
|
||||
newGroup.expense = add(newGroup.expense, newAmount)
|
||||
|
||||
if (newType === 1)
|
||||
next.total_income = add(next.total_income, newAmount)
|
||||
if (newType === 0)
|
||||
next.total_expense = add(next.total_expense, newAmount)
|
||||
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function handleDeleteRecord(id: number) {
|
||||
// 直接更新本地状态
|
||||
setMoneyRecords((prev) => {
|
||||
const next = structuredClone(prev)
|
||||
|
||||
// 遍历每个分组
|
||||
next.records.forEach((group) => {
|
||||
const index = group.records.findIndex(r => r.id === id)
|
||||
if (index !== -1) {
|
||||
const record = group.records[index]
|
||||
|
||||
// 更新分组金额
|
||||
if (record.category_type === 1) {
|
||||
group.income = sub(group.income, record.amount)
|
||||
next.total_income = sub(next.total_income, record.amount)
|
||||
}
|
||||
else if (record.category_type === 0) {
|
||||
group.expense = sub(group.expense, record.amount)
|
||||
next.total_expense = sub(next.total_expense, record.amount)
|
||||
}
|
||||
|
||||
// 删除记录
|
||||
group.records.splice(index, 1)
|
||||
}
|
||||
})
|
||||
|
||||
// 删除空分组
|
||||
next.records = next.records.filter(g => g.records.length > 0)
|
||||
|
||||
return next
|
||||
})
|
||||
|
||||
// 异步请求删除 API
|
||||
deleteMoneyRecordApi({ id }).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<View
|
||||
@@ -307,19 +71,7 @@ export default function Money() {
|
||||
</View>
|
||||
|
||||
<View className="mt-3 m-4 ">
|
||||
|
||||
<View className="flex flex-row gap-4">
|
||||
<View className="flex-1">
|
||||
<Text className="text-gray-500 text-sm">余额</Text>
|
||||
<Text
|
||||
className="text-lg font-semibold mt-0.5"
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
0.00
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="flex-1">
|
||||
<Text className="text-gray-500 text-sm">收入</Text>
|
||||
<Text
|
||||
@@ -327,7 +79,7 @@ export default function Money() {
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
0.00
|
||||
{moneyRecordStore.total_income}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -338,7 +90,7 @@ export default function Money() {
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
0.00
|
||||
{moneyRecordStore.total_expense}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -374,13 +126,15 @@ export default function Money() {
|
||||
|
||||
<View className="flex-1 mt-2">
|
||||
<TransactionsList
|
||||
records={moneyRecords.records}
|
||||
records={moneyRecordStore.records}
|
||||
onUpdate={(data) => {
|
||||
setUpdateItem(data)
|
||||
setShowAddDrawer(true)
|
||||
router.navigate({
|
||||
pathname: '/money/record/editor',
|
||||
params: { id: data.id },
|
||||
})
|
||||
}}
|
||||
onDelete={(id) => {
|
||||
handleDeleteRecord(id)
|
||||
moneyRecordStore.deleteMoneyRecord(id)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -388,35 +142,16 @@ export default function Money() {
|
||||
<Fab
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
// setShowAddDrawer(true)
|
||||
router.navigate('/money/record/editor')
|
||||
}}
|
||||
>
|
||||
<FabIcon as={AddIcon} size="xl" />
|
||||
</Fab>
|
||||
|
||||
<AddMoneyDrawer
|
||||
isOpen={showAddDrawer}
|
||||
updateData={updateItem}
|
||||
onClose={() => {
|
||||
setShowAddDrawer(false)
|
||||
setUpdateItem(null)
|
||||
}}
|
||||
onConfirm={(res) => {
|
||||
setShowAddDrawer(false)
|
||||
if (updateItem !== null) {
|
||||
setUpdateItem(null)
|
||||
handleUpdateMoneyRecord(res)
|
||||
}
|
||||
else {
|
||||
handleAddMoneyRecord(res)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<ADatePicker
|
||||
show={showDatePicker}
|
||||
mode="month"
|
||||
date={searchParams.record_time}
|
||||
onConfirm={(val) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||
import type { MoneyRecordBase, MoneyRecordItem } from '@/types/money/record'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { useState } from 'react'
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Text, TouchableOpacity, View } from 'react-native'
|
||||
import { LucideIcon } from '@/components/common/ao'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import PagerTabs from '@/components/custom/PagerTabs'
|
||||
import { insertMoneyRecordApi } from '@/request/api/money/record'
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
import { useMoneyRecordStore } from '@/store/moneyRecord'
|
||||
import { getCurrentDate, toDateString } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
import AmountKeyboard from './components/AmountKeyboard'
|
||||
import CategoryGrid from './components/CategoryGrid'
|
||||
|
||||
export default function MoenyRecordEditor() {
|
||||
const router = useRouter()
|
||||
|
||||
const moneyCategoryStore = useMoneyCategoryStore()
|
||||
const expense = useMoneyCategoryStore(state => state.expense)
|
||||
const income = useMoneyCategoryStore(state => state.income)
|
||||
const moneyRecordStore = useMoneyRecordStore()
|
||||
|
||||
console.log(moneyCategoryStore.categories)
|
||||
console.log(expense)
|
||||
console.log(income)
|
||||
const router = useRouter()
|
||||
const expense = moneyCategoryStore.getExpenseCategories()
|
||||
const income = moneyCategoryStore.getIncomeCategories()
|
||||
|
||||
const { id: updateId } = useLocalSearchParams<{ id: string }>()
|
||||
|
||||
// 选择顺序:0 支出 1收入
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
// 自定义键盘
|
||||
const [showKeydown, setShowKeydown] = useState(false)
|
||||
// 选中分类
|
||||
@@ -42,6 +41,25 @@ export default function MoenyRecordEditor() {
|
||||
|
||||
const [moneyRecord, setMoneyRecord] = useState<MoneyRecordBase>(defaultMoneyRecord)
|
||||
|
||||
useEffect(() => {
|
||||
if (updateId) {
|
||||
const updateData = moneyRecordStore.getMoneyRecordById(Number(updateId))
|
||||
if (updateData !== null) {
|
||||
setMoneyRecord({
|
||||
...updateData,
|
||||
record_time: toDateString(updateData.record_time),
|
||||
})
|
||||
setShowKeydown(true)
|
||||
|
||||
const moneyCategory = moneyCategoryStore.getCategoryById(updateData.category_id)
|
||||
|
||||
if (moneyCategory != null) {
|
||||
setSelectedCategory(moneyCategory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 保存记录
|
||||
function handleSaveRecord() {
|
||||
if (selectedCategory === null) {
|
||||
@@ -55,7 +73,39 @@ export default function MoenyRecordEditor() {
|
||||
toast.show({
|
||||
title: '请输入金额',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const amount = moneyRecord.amount.replace(/^-+/, '')
|
||||
|
||||
insertMoneyRecordApi({
|
||||
...moneyRecord,
|
||||
amount,
|
||||
category_id: selectedCategory.id,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
const result: MoneyRecordItem = {
|
||||
...res.data,
|
||||
category_name: selectedCategory.name,
|
||||
category_icon: selectedCategory.icon,
|
||||
category_type: selectedCategory.type,
|
||||
}
|
||||
|
||||
moneyRecordStore.addMoneyRecord(result)
|
||||
|
||||
router.back()
|
||||
}
|
||||
else {
|
||||
toast.show({
|
||||
title: res.message || '保存失败',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('发生异常啦')
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -70,6 +120,7 @@ export default function MoenyRecordEditor() {
|
||||
/>
|
||||
|
||||
<PagerTabs
|
||||
selectedIndex={selectedCategory?.type}
|
||||
left={(
|
||||
<TouchableOpacity className="w-[30%] flex items-center px-4 flex-row">
|
||||
<LucideIcon icon="LayoutList" />
|
||||
@@ -83,7 +134,7 @@ export default function MoenyRecordEditor() {
|
||||
router.back()
|
||||
}}
|
||||
>
|
||||
<Text className="ml-auto text-gray-400">取消</Text>
|
||||
<Text className="ml-auto text-base text-gray-400">取消</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
routes={[
|
||||
|
||||
Reference in new issue
Block a user