u
This commit is contained in:
1 parent
18e9d94b77
commit
1f82f1dd1f
16 files changed
+1048
-297
No files matched your search
@@ -1,39 +1,62 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { Button, ScrollView, Text, View } from 'react-native'
|
||||
import { useState, useCallback } from 'react'
|
||||
import { Pressable, Text, View, FlatList } from 'react-native'
|
||||
import { AddIcon, GlobeIcon, Icon, PlayIcon, SettingsIcon } from '@/components/ui/icon'
|
||||
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
|
||||
|
||||
export default function ScrollToExample() {
|
||||
const scrollRef = useRef<ScrollView>(null)
|
||||
const [positions, setPositions] = useState<Record<string, number>>({})
|
||||
export default function Example() {
|
||||
const data = Array.from({ length: 1000 }, (_, i) => i + 1)
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null) // 控制哪个菜单打开
|
||||
|
||||
const handleLayout = (name: string, event: any) => {
|
||||
const { y } = event.nativeEvent.layout
|
||||
setPositions(prev => ({ ...prev, [name]: y }))
|
||||
}
|
||||
|
||||
const scrollToField = (name: string) => {
|
||||
const y = positions[name]
|
||||
if (typeof y === 'number' && scrollRef.current) {
|
||||
scrollRef.current.scrollTo({ y, animated: true })
|
||||
}
|
||||
}
|
||||
const renderItem = useCallback(
|
||||
({ item: index }) => (
|
||||
<View style={{ marginVertical: 10 }}>
|
||||
<Menu
|
||||
placement="top"
|
||||
offset={5}
|
||||
disabledKeys={['Settings']}
|
||||
isOpen={openIndex === index}
|
||||
onOpen={() => setOpenIndex(index)}
|
||||
onClose={() => setOpenIndex(null)}
|
||||
trigger={({ onPress, ...triggerProps }) => (
|
||||
<Pressable {...triggerProps} onLongPress={onPress}>
|
||||
<Text>Menu {index}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
>
|
||||
{openIndex === index && (
|
||||
<>
|
||||
<MenuItem key="Add account" textValue="Add account">
|
||||
<Icon as={AddIcon} size="sm" className="mr-2" />
|
||||
<MenuItemLabel size="sm">Add account - {index}</MenuItemLabel>
|
||||
</MenuItem>
|
||||
<MenuItem key="Community" textValue="Community">
|
||||
<Icon as={GlobeIcon} size="sm" className="mr-2" />
|
||||
<MenuItemLabel size="sm">Community - {index}</MenuItemLabel>
|
||||
</MenuItem>
|
||||
<MenuItem key="Plugins" textValue="Plugins">
|
||||
<Icon as={PlayIcon} size="sm" className="mr-2" />
|
||||
<MenuItemLabel size="sm">Plugins - {index}</MenuItemLabel>
|
||||
</MenuItem>
|
||||
<MenuItem key="Settings" textValue="Settings">
|
||||
<Icon as={SettingsIcon} size="sm" className="mr-2" />
|
||||
<MenuItemLabel size="sm">Settings - {index}</MenuItemLabel>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</View>
|
||||
),
|
||||
[openIndex]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 40 }}>
|
||||
<Button title="滚动到 Password" onPress={() => scrollToField('password')} />
|
||||
|
||||
<ScrollView ref={scrollRef} style={{ marginTop: 20 }}>
|
||||
<View onLayout={e => handleLayout('username', e)} style={{ height: 300, backgroundColor: '#f9c2ff', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Username 区域</Text>
|
||||
</View>
|
||||
|
||||
<View onLayout={e => handleLayout('email', e)} style={{ height: 300, backgroundColor: '#c2f9ff', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Email 区域</Text>
|
||||
</View>
|
||||
|
||||
<View onLayout={e => handleLayout('password', e)} style={{ height: 300, backgroundColor: '#ffc2c2', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text>Password 区域</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<FlatList
|
||||
data={data}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.toString()}
|
||||
initialNumToRender={20}
|
||||
maxToRenderPerBatch={20}
|
||||
windowSize={10}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MoneyCategoryItem, QueryMoneyCategoryResult } from '@/types/money/category'
|
||||
import type { MoneyRecordBase, MoneyRecordItem } from '@/types/money/record'
|
||||
import Big from 'big.js'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { FlatList, Keyboard, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { AInput, LucideIcon } from '@/components/common/ao'
|
||||
@@ -15,14 +15,15 @@ import {
|
||||
} from '@/components/ui/drawer'
|
||||
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
|
||||
import { queryMoneyCategoryApi } from '@/request/api/money/category'
|
||||
import { insertMoneyRecordApi } from '@/request/api/money/record'
|
||||
import { getCurrentDate } from '@/utils/time'
|
||||
import { insertMoneyRecordApi, updateMoneyRecordApi } from '@/request/api/money/record'
|
||||
import { getCurrentDate, toDateString } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
||||
|
||||
interface AddMoneyDrawerProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
updateData?: MoneyRecordItem | null
|
||||
onClose?: () => void
|
||||
onConfirm?: (result: MoneyRecordItem) => void
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ export default function AddMoneyDrawer({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
updateData,
|
||||
}: AddMoneyDrawerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
// 选择顺序:0 支出 1收入
|
||||
@@ -114,6 +116,35 @@ export default function AddMoneyDrawer({
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (updateData) {
|
||||
const type = updateData.category_type
|
||||
setSelectedIndex(type)
|
||||
setMoneyRecord({
|
||||
...updateData,
|
||||
record_time: toDateString(updateData.record_time),
|
||||
})
|
||||
setShowKeydown(true)
|
||||
|
||||
if (type === 0) {
|
||||
// 支出
|
||||
categories.expense.forEach((item) => {
|
||||
if (item.id === updateData.category_id) {
|
||||
setSelectedCategory(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (type === 1) {
|
||||
// 收入
|
||||
categories.income.forEach((item) => {
|
||||
if (item.id === updateData.category_id) {
|
||||
setSelectedCategory(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [categories])
|
||||
|
||||
function resetData() {
|
||||
// 清空键盘记录
|
||||
setKeydownInput('')
|
||||
@@ -133,7 +164,7 @@ export default function AddMoneyDrawer({
|
||||
function onMainDrawerClose() {
|
||||
resetData()
|
||||
|
||||
onClose()
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
// 计算过程
|
||||
@@ -314,7 +345,6 @@ export default function AddMoneyDrawer({
|
||||
}
|
||||
}
|
||||
|
||||
// 保存
|
||||
// 保存
|
||||
function saveRecord() {
|
||||
if (selectedCategory === null) {
|
||||
@@ -334,33 +364,62 @@ export default function AddMoneyDrawer({
|
||||
// 去掉金额负号
|
||||
const amount = moneyRecord.amount.replace(/^-+/, '')
|
||||
|
||||
insertMoneyRecordApi({
|
||||
...moneyRecord,
|
||||
amount,
|
||||
category_id: selectedCategory.id,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
resetData()
|
||||
const isUpdate = updateData && updateData !== null
|
||||
|
||||
if (isUpdate) {
|
||||
updateMoneyRecordApi({
|
||||
id: moneyRecord.id,
|
||||
category_id: selectedCategory.id,
|
||||
amount,
|
||||
remark: moneyRecord.remark,
|
||||
record_time: moneyRecord.record_time,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
const result: MoneyRecordItem = {
|
||||
...res.data,
|
||||
...moneyRecord,
|
||||
category_id: selectedCategory.id,
|
||||
category_name: selectedCategory.name,
|
||||
category_icon: selectedCategory.icon,
|
||||
category_type: selectedCategory.type,
|
||||
}
|
||||
|
||||
onConfirm?.(result)
|
||||
|
||||
resetData()
|
||||
}
|
||||
else {
|
||||
toast.show({
|
||||
title: res.message || '保存失败',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
else {
|
||||
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,
|
||||
}
|
||||
|
||||
onConfirm?.(result)
|
||||
resetData()
|
||||
}
|
||||
else {
|
||||
toast.show({
|
||||
title: res.message || '保存失败',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import type { MoneyRecordGroup } from '@/types/money/record'
|
||||
import React from 'react'
|
||||
import { SectionList, Text, View } from 'react-native'
|
||||
import type { MoneyRecordGroup, MoneyRecordItem } from '@/types/money/record'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Pressable, SectionList, Text, View } from 'react-native'
|
||||
import { LucideIcon } from '@/components/common/ao'
|
||||
import EmptyState from '@/components/common/EmptyStatus'
|
||||
import { Badge, BadgeText } from '@/components/ui/badge'
|
||||
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
|
||||
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
||||
|
||||
interface StaticTransactionsProps {
|
||||
records: MoneyRecordGroup[]
|
||||
onUpdate?: (data: MoneyRecordItem) => void
|
||||
onDelete?: (id: number) => void
|
||||
}
|
||||
|
||||
export default function StaticTransactions({ records }: StaticTransactionsProps) {
|
||||
export default function StaticTransactions({
|
||||
records,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
}: StaticTransactionsProps) {
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null)
|
||||
|
||||
if (records.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
@@ -33,7 +44,7 @@ export default function StaticTransactions({ records }: StaticTransactionsProps)
|
||||
stickySectionHeadersEnabled={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderSectionHeader={({ section }) => (
|
||||
<View className="bg-white py-2 px-1 border-b border-gray-100 mb-2">
|
||||
<View className="bg-white py-2 px-4 border-b border-gray-100 mb-2">
|
||||
<View className="flex-row items-center justify-between">
|
||||
<Text className="text-gray-700 font-semibold text-base">
|
||||
{section.title}
|
||||
@@ -63,31 +74,82 @@ export default function StaticTransactions({ records }: StaticTransactionsProps)
|
||||
</View>
|
||||
)}
|
||||
|
||||
renderItem={({ item }) => (
|
||||
<View className="flex-row items-center mb-4 gap-4">
|
||||
<View className="w-10 h-10 rounded-full bg-gray-100 items-center justify-center">
|
||||
<MoneyCategoryIcon name={item.category_icon} size={22} color="#000" />
|
||||
</View>
|
||||
renderItem={({ item }) => {
|
||||
const isOpen = item.id === selectedId
|
||||
|
||||
<View className="flex-1">
|
||||
<Text className="text-gray-800 font-medium text-base">
|
||||
{item.category_name}
|
||||
</Text>
|
||||
return (
|
||||
<Menu
|
||||
placement="bottom"
|
||||
offset={5}
|
||||
isOpen={isOpen}
|
||||
onClose={() => setSelectedId(null)}
|
||||
trigger={({ onLongPress, onPressOut, delayLongPress, android_ripple, ...triggerProps }) => (
|
||||
<Pressable
|
||||
{...triggerProps}
|
||||
onLongPress={() => setSelectedId(item.id)}
|
||||
delayLongPress={200}
|
||||
android_ripple={undefined}
|
||||
>
|
||||
<View
|
||||
className="flex-row items-center gap-4 py-2 px-5"
|
||||
style={{
|
||||
backgroundColor: item.id === selectedId ? '#f2f2f2' : 'transparent',
|
||||
}}
|
||||
>
|
||||
<View className="w-10 h-10 rounded-full bg-gray-100 items-center justify-center">
|
||||
<MoneyCategoryIcon name={item.category_icon} size={22} color="#000" />
|
||||
</View>
|
||||
|
||||
{item.remark && (
|
||||
<Text className="text-gray-400 text-sm">{item.remark}</Text>
|
||||
<View className="flex-1">
|
||||
<Text className="text-gray-800 font-medium text-base">{item.category_name}</Text>
|
||||
{item.remark && <Text className="text-gray-400 text-sm">{item.remark}</Text>}
|
||||
</View>
|
||||
|
||||
<View className="flex-row flex">
|
||||
<Text className="text-sm text-gray-500">
|
||||
{item.category_type === 1 ? '+' : '-'}
|
||||
{' '}
|
||||
{item.amount}
|
||||
</Text>
|
||||
</View>
|
||||
<Badge size='sm' variant='solid' action='muted'>
|
||||
<BadgeText>宝贝</BadgeText>
|
||||
</Badge>
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View className="flex-row flex">
|
||||
<Text className="text-sm text-gray-500">
|
||||
{item.category_type === 0 ? '+' : '-'}
|
||||
{' '}
|
||||
{item.amount}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
>
|
||||
{isOpen && (
|
||||
<>
|
||||
<MenuItem
|
||||
key="update"
|
||||
textValue="update"
|
||||
onPress={() => {
|
||||
onUpdate?.(item)
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="Pencil" size={14} className="mr-2" />
|
||||
<MenuItemLabel size="sm">
|
||||
修改
|
||||
</MenuItemLabel>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
key="delete"
|
||||
textValue="delete"
|
||||
onPress={() => {
|
||||
onDelete?.(item.id)
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="Trash2" size={14} className="mr-2" />
|
||||
<MenuItemLabel size="sm">
|
||||
删除
|
||||
</MenuItemLabel>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { MoneyRecordGroup, MoneyRecordItem, QueryMoneyRecordsResult } from '@/types/money/record'
|
||||
import Big from 'big.js'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Text, TouchableOpacity, View } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
@@ -7,9 +6,9 @@ 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 { queryMoneyRecordsApi } from '@/request/api/money/record'
|
||||
import { deleteMoneyRecordApi, queryMoneyRecordsApi } from '@/request/api/money/record'
|
||||
import { add, sub } from '@/utils/math'
|
||||
import { getCurrentYearMonth } from '@/utils/time'
|
||||
import { getCurrentYearMonth, isInMonth } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
import AddMoneyDrawer from './components/AddDrawer'
|
||||
import TransactionsList from './components/TransactionsList'
|
||||
@@ -30,6 +29,8 @@ export default function Money() {
|
||||
total_income: '0',
|
||||
records: [],
|
||||
})
|
||||
// 更新
|
||||
const [updateItem, setUpdateItem] = useState<MoneyRecordItem | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
queryMoneyRecordsApi({
|
||||
@@ -53,6 +54,11 @@ export default function Money() {
|
||||
const recordTime = data.record_time
|
||||
|
||||
setMoneyRecords((prev) => {
|
||||
// 不在当前月份 → 不添加,直接返回
|
||||
if (!isInMonth(recordTime, searchParams.record_time)) {
|
||||
return prev
|
||||
}
|
||||
|
||||
const next = structuredClone(prev)
|
||||
|
||||
// 查找分组
|
||||
@@ -64,12 +70,12 @@ export default function Money() {
|
||||
group.records.unshift(data)
|
||||
|
||||
// 更新分组 & 总计
|
||||
if (data.category_type === 0) {
|
||||
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 === 1) {
|
||||
else if (data.category_type === 0) {
|
||||
// 支出
|
||||
group.expense = add(group.expense, data.amount)
|
||||
next.total_expense = add(next.total_expense, data.amount)
|
||||
@@ -79,8 +85,8 @@ export default function Money() {
|
||||
}
|
||||
|
||||
// === 无分组,新建 ===
|
||||
const income = data.category_type === 0 ? data.amount : '0'
|
||||
const expense = data.category_type === 1 ? data.amount : '0'
|
||||
const expense = data.category_type === 0 ? data.amount : '0'
|
||||
const income = data.category_type === 1 ? data.amount : '0'
|
||||
|
||||
const newGroup: MoneyRecordGroup = {
|
||||
record_time: recordTime,
|
||||
@@ -94,11 +100,9 @@ export default function Money() {
|
||||
const index = next.records.findIndex(g => g.record_time < recordTime)
|
||||
|
||||
if (index === -1) {
|
||||
// 所有日期都比它大 → 插入最后
|
||||
next.records.push(newGroup)
|
||||
}
|
||||
else {
|
||||
// 插入正确位置
|
||||
next.records.splice(index, 0, newGroup)
|
||||
}
|
||||
|
||||
@@ -114,6 +118,164 @@ export default function Money() {
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
className="flex-1"
|
||||
@@ -122,7 +284,7 @@ export default function Money() {
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<View className="flex flex-row justify-between items-center px-4">
|
||||
<View className="flex flex-row justify-between items-center px-3">
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowDatePicker(true)}
|
||||
className="flex flex-row items-center gap-1"
|
||||
@@ -198,8 +360,17 @@ export default function Money() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="flex-1 px-4 mt-2">
|
||||
<TransactionsList records={moneyRecords.records} />
|
||||
<View className="flex-1 mt-2">
|
||||
<TransactionsList
|
||||
records={moneyRecords.records}
|
||||
onUpdate={(data) => {
|
||||
setUpdateItem(data)
|
||||
setShowAddDrawer(true)
|
||||
}}
|
||||
onDelete={(id) => {
|
||||
handleDeleteRecord(id)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Fab
|
||||
@@ -213,12 +384,20 @@ export default function Money() {
|
||||
|
||||
<AddMoneyDrawer
|
||||
isOpen={showAddDrawer}
|
||||
updateData={updateItem}
|
||||
onClose={() => {
|
||||
setShowAddDrawer(false)
|
||||
setUpdateItem(null)
|
||||
}}
|
||||
onConfirm={(res) => {
|
||||
setShowAddDrawer(false)
|
||||
handleAddMoneyRecord(res)
|
||||
if (updateItem !== null) {
|
||||
setUpdateItem(null)
|
||||
handleUpdateMoneyRecord(res)
|
||||
}
|
||||
else {
|
||||
handleAddMoneyRecord(res)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function User() {
|
||||
</View>
|
||||
|
||||
<AButton onPress={() => {
|
||||
router.replace('/auth/login')
|
||||
router.replace('/auth')
|
||||
}}
|
||||
>
|
||||
跳转到登陆页面
|
||||
|
||||
Reference in new issue
Block a user