This commit is contained in:
oneao committed 2026-01-13 22:18:01 +08:00
1 parent 6277fb2f9e
commit 8ecac0d7dd
270 files changed
+40438 -529

No files matched your search

@@ -0,0 +1,184 @@
import type { SortableGridRenderItem } from 'react-native-sortables'
import type { MoneyCategoryItem } from '@/types/money/category'
import { useCallback, useEffect, useState } from 'react'
import { Pressable, Text, Vibration, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables'
import { AButton, ATab, LucideIcon } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import CustomHeader from '@/components/custom/Header'
import { useMoneyCategoryStore } from '@/store/moneyCategory'
export default function MoneyCategorySetting() {
const [selectedTab, setSelectedTab] = useState(0)
const moneyCategoryStore = useMoneyCategoryStore()
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
const [categories, setCategories] = useState<MoneyCategoryItem[]>([])
const [draggingId, setDraggingId] = useState<string>('')
const [draggingDeleteZone, setDraggingDeleteZone] = useState<boolean>(false)
const [deleteTimer, setDeleteTimer] = useState<number | null>(null)
useEffect(() => {
setCategories(selectedTab === 0 ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
}, [selectedTab])
const renderItem = useCallback<SortableGridRenderItem<MoneyCategoryItem>>(
({ item }) => (
<Pressable
onPress={() => {
console.log('点击进入编辑')
}}
className="flex flex-col items-center justify-center"
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F3F4F6',
}}
>
<MoneyCategoryIcon size={28} name={item.icon} />
</View>
<Text style={{
marginBottom: 4,
fontSize: 14,
}}
>
{item.name}
</Text>
</Pressable>
),
[],
)
return (
<View className="flex-1">
<CustomHeader
title="类别设置"
/>
<View className="flex-1">
<View className="pt-2 mb-4 px-3 flex flex-row items-center justify-between">
<ATab
defaultIndex={selectedTab}
onChange={(index) => {
setSelectedTab(index)
}}
data={[
{
key: 'income',
label: '收入',
},
{
key: 'expense',
label: '支出',
},
]}
/>
<AButton
className="rounded-full"
size="sm"
leftIcon={{
icon: 'Plus',
color: '#fff',
clickable: false,
}}
>
新增
</AButton>
</View>
<View className="flex-1">
<Sortable.MultiZoneProvider>
<Sortable.PortalProvider enabled>
<Animated.ScrollView
ref={scrollableRef}
>
<Sortable.Grid
columnGap={10}
rowGap={10}
columns={4}
data={categories}
renderItem={renderItem}
scrollableRef={scrollableRef}
autoScrollActivationOffset={200}
showDropIndicator
onDragStart={(data) => {
Vibration.vibrate(50)
setDraggingId(data.key)
}}
onDragEnd={(data) => {
setDraggingId('')
if (draggingDeleteZone)
return
const newOrder = data.data
moneyCategoryStore.updateCategories(newOrder, selectedTab)
}}
/>
</Animated.ScrollView>
<Sortable.BaseZone
onItemEnter={() => {
if (!draggingId)
return
setDraggingDeleteZone(true)
Vibration.vibrate(50)
// 设置 1 秒后删除
const timer = setTimeout(() => {
const deleteId = Number(draggingId)
setCategories(prev => prev.filter(c => c.id !== deleteId))
moneyCategoryStore.deleteCategoryById(deleteId, selectedTab)
setDraggingId('')
setDraggingDeleteZone(false)
setDeleteTimer(null)
}, 1000)
setDeleteTimer(timer as unknown as number)
}}
onItemLeave={() => {
setDraggingDeleteZone(false)
if (deleteTimer) {
clearTimeout(deleteTimer)
setDeleteTimer(null)
}
}}
onItemDrop={() => {
setDraggingDeleteZone(false)
if (deleteTimer) {
clearTimeout(deleteTimer)
setDeleteTimer(null)
}
}}
minActivationDistance={40}
>
<View
className={`flex flex-row items-center justify-center h-14 gap-2 ${
draggingDeleteZone ? 'bg-red-600' : 'bg-red-400'
}`}
>
<LucideIcon icon="Trash" size={18} color="#fff" />
<Text className="text-base text-white ">
拖到这里删除
</Text>
</View>
</Sortable.BaseZone>
</Sortable.PortalProvider>
</Sortable.MultiZoneProvider>
</View>
</View>
</View>
)
}
@@ -0,0 +1,102 @@
import { useLocalSearchParams, useRouter } from 'expo-router'
import { useState } from 'react'
import { Text, View } from 'react-native'
import { AButton, ACell, ACellGroup, AModal } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import CustomHeader from '@/components/custom/Header'
import { useMoneyRecordStore } from '@/store/moneyRecord'
import { useUserStore } from '@/store/user'
import { toDateString, toDateTimeString } from '@/utils/time'
export default function MoneyRecordDetail() {
const router = useRouter()
const moneyRecordStore = useMoneyRecordStore()
const userStore = useUserStore()
const { id: detailId } = useLocalSearchParams<{ id: string }>()
const moneryRecord = moneyRecordStore.getMoneyRecordById(Number(detailId))
const [showDeleteModal, setShowDeleteModal] = useState(false)
if (moneryRecord === null) {
return null
}
return (
<>
<View className="flex-1">
<CustomHeader title="账单详情" />
<View className="flex-1">
<View className="flex flex-col items-center">
<MoneyCategoryIcon name={moneryRecord.category_icon} size={56} />
<Text className="mt-1">{moneryRecord.category_name}</Text>
</View>
<ACellGroup showDivider={false} className="mt-4">
<ACell title="类别" rightText={moneryRecord.category_type === 0 ? '支出' : '收入'} />
<ACell title="金额" rightText={moneryRecord.amount} />
<ACell title="备注" rightText={moneryRecord.remark} />
<ACell title="日期" rightText={toDateString(moneryRecord.record_time)} />
<ACell title="操作日期" rightText={toDateTimeString(moneryRecord.update_time || moneryRecord.create_time)} />
<ACell
title="创建人"
rightText={
userStore.userInfo?.id === moneryRecord.user_id
? '自己'
: moneryRecord.username
}
/>
</ACellGroup>
{userStore.userInfo?.id === moneryRecord.user_id && (
<View className="flex flex-row px-4 mt-4 gap-4">
<AButton
className="flex-1"
action="negative"
variant="outline"
onPress={() => {
setShowDeleteModal(true)
}}
>
删除
</AButton>
<AButton
className="flex-1"
onPress={() => {
router.navigate({
pathname: '/money/record/editor',
params: { id: detailId },
})
}}
>
编辑
</AButton>
</View>
)}
</View>
</View>
<AModal
isOpen={showDeleteModal}
title="提示"
body="确认删除该条账单?"
onConfirm={() => {
setShowDeleteModal(false)
moneyRecordStore.deleteMoneyRecord(Number(detailId))
router.back()
}}
onCancel={() => {
setShowDeleteModal(false)
}}
onClose={() => {
setShowDeleteModal(false)
}}
/>
</>
)
}
@@ -0,0 +1,359 @@
import Big from 'big.js'
import { useEffect, useState } from 'react'
import { Keyboard, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
import { ADatePicker, AInput, LucideIcon } from '@/components/common/ao/index'
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
import { getCurrentDate } from '@/utils/time'
interface KeydownItem {
key: string | number
content: React.ReactNode
}
const keys: KeydownItem[] = [
{ key: '1', content: <Text className="text-2xl">1</Text> },
{ key: '2', content: <Text className="text-2xl">2</Text> },
{ key: '3', content: <Text className="text-2xl">3</Text> },
{ key: 'del', content: <LucideIcon icon="Delete" size={22} strokeWidth={1.5} clickable={false} /> },
{ key: '4', content: <Text className="text-2xl">4</Text> },
{ key: '5', content: <Text className="text-2xl">5</Text> },
{ key: '6', content: <Text className="text-2xl">6</Text> },
{ key: '+', content: <LucideIcon icon="Plus" size={22} strokeWidth={1.5} clickable={false} /> },
{ key: '7', content: <Text className="text-2xl">7</Text> },
{ key: '8', content: <Text className="text-2xl">8</Text> },
{ key: '9', content: <Text className="text-2xl">9</Text> },
{ key: '-', content: <LucideIcon icon="Minus" size={22} strokeWidth={1.5} clickable={false} /> },
{ key: 'clear', content: (
<View className="w-full h-full flex items-center justify-center">
<Text className="text-xl text-gray-600">重置</Text>
</View>
) },
{ key: '0', content: <Text className="text-2xl">0</Text> },
{ key: '.', content: <LucideIcon icon="Dot" size={22} strokeWidth={1.5} clickable={false} /> },
{ key: '/', content: (
<View className="w-full h-full flex items-center justify-center">
<Text className="text-2xl">完成</Text>
</View>
) },
]
interface AmountKeyboardProps {
show?: boolean
record_time?: string
amount?: string
remark?: string
// 回调:当键盘输入或修改数据时触发
onChange?: (data: { record_time: string, amount: string, remark: string }) => void
// 可选:点击完成按钮触发
onDone?: () => void
}
export default function AmountKeyboard({
show = false,
record_time = '',
amount = '',
remark = '',
onChange,
onDone,
}: AmountKeyboardProps) {
// 自定义键盘计算过程
const [keydownInput, setKeydownInput] = useState('')
// 日期选择
const [showDatePicker, setShowDatePicker] = useState(false)
// 备注聚焦
const [remarkIsFocused, setRemarkIsFocused] = useState(false)
// 键盘高度
const keyboardHeight = useKeyboardHeight()
// 使用一个对象管理三个字段
const [internalData, setInternalData] = useState({
record_time: record_time || getCurrentDate(),
amount: amount || '0',
remark: remark || '',
})
// 如果父组件 props 变化,需要同步
useEffect(() => {
setInternalData({
record_time: record_time || getCurrentDate(),
amount: amount || '0',
remark: remark || '',
})
}, [record_time, amount, remark])
const handleChange = (newData: Partial<typeof internalData>) => {
const updated = { ...internalData, ...newData }
setInternalData(updated)
onChange?.(updated)
}
// 计算过程
const handlePress = (key: string | number) => {
setKeydownInput((prev) => {
let newInput = prev
if (key === 'del') {
newInput = prev.slice(0, -1)
}
else if (key === 'clear') {
const currentDate = getCurrentDate()
handleChange({ record_time: currentDate, amount: '', remark: '' })
return ''
}
else if (key === '/') {
onDone?.()
return prev
}
else {
const keyStr = key.toString()
// 检查是否要输入运算符(+ 或 -)
if (keyStr === '+' || keyStr === '-') {
// 如果当前输入为空,不允许以运算符开头
if (prev === '') {
return prev
}
// 获取最后一个字符
const lastChar = prev.slice(-1)
// 如果最后一个字符已经是运算符,则替换而不是追加
if (lastChar === '+' || lastChar === '-') {
newInput = prev.slice(0, -1) + keyStr
}
// 如果最后一个字符是小数点,不允许在小数点后直接加运算符
else if (lastChar === '.') {
return prev
}
else {
newInput = prev + keyStr
}
}
// 检查是否要输入小数点(.)
else if (keyStr === '.') {
// 如果当前输入为空,不允许以小数点开头,改为输入"0."
if (prev === '') {
newInput = '0.'
}
else {
// 获取最后一个字符
const lastChar = prev.slice(-1)
// 如果最后一个字符是运算符,则需要在运算符后添加"0."而不是单独的"."
if (lastChar === '+' || lastChar === '-') {
newInput = `${prev}0.`
}
else {
// 检查当前数字是否已经包含小数点
const parts = prev.split(/[+-]/)
const currentNumber = parts[parts.length - 1]
// 如果当前数字中还没有小数点,可以添加
if (!currentNumber.includes('.')) {
newInput = `${prev}.`
}
else {
// 已经有小数点了,不重复添加
newInput = prev
}
}
}
}
// 处理数字输入
else if (/^\d$/.test(keyStr)) {
// 检查当前数字的小数位数
const parts = prev.split(/[+-]/)
const currentNumber = parts[parts.length - 1]
// 如果当前数字包含小数点,检查小数位数
if (currentNumber.includes('.')) {
const decimalParts = currentNumber.split('.')
// 如果小数部分已经达到2位,不允许继续输入
if (decimalParts.length > 1 && decimalParts[1].length >= 2) {
newInput = prev // 不改变输入
}
else {
newInput = prev + keyStr
}
}
else {
// 没有小数点,正常输入数字
newInput = prev + keyStr
}
}
else {
// 对于其他字符,正常追加
newInput = prev + keyStr
}
}
// 计算结果
try {
const computed = calculateWithBig(newInput)
handleChange({ amount: computed })
}
catch (e) {
handleChange({ amount: '0' })
}
return newInput
})
}
function calculateWithBig(expression: string): string {
if (!expression.trim())
return '0'
// 移除表达式末尾的运算符(+或-),避免不完整的表达式
let cleanExpression = expression.trim()
const lastChar = cleanExpression.slice(-1)
if (lastChar === '+' || lastChar === '-') {
cleanExpression = cleanExpression.slice(0, -1)
}
if (!cleanExpression)
return '0'
try {
// 使用更智能的解析方式,支持基本的加减运算
const numbers = cleanExpression.split(/[+-]/).filter(Boolean)
const operators = cleanExpression.split(/[^+-]/).filter(Boolean)
if (numbers.length === 0)
return '0'
let result = new Big(numbers[0])
for (let i = 0; i < operators.length; i++) {
const operator = operators[i]
const nextNumber = new Big(numbers[i + 1])
if (operator === '+') {
result = result.plus(nextNumber)
}
else if (operator === '-') {
result = result.minus(nextNumber)
}
}
// 限制结果为最多两位小数
return result.toFixed(2)
}
catch (error) {
// 如果解析失败,尝试只计算数字部分
const tokens = expression.match(/[+-]?\d+(\.\d+)?/g)
if (!tokens || tokens.length === 0)
return '0'
let total = new Big(0)
tokens.forEach((token) => {
total = total.plus(new Big(token))
})
// 限制结果为最多两位小数
return total.toFixed(2)
}
}
if (!show) {
return null
}
return (
<View style={{
backgroundColor: '#fff',
elevation: 20,
}}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View className="h-28 flex pl-4 pr-4 justify-center">
<View className="flex flex-row items-center justify-between">
<Pressable
className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70"
onPress={() => setShowDatePicker(true)}
>
<LucideIcon icon="Calendar" size={16} color="#60a5fa" clickable={false} />
<Text className="text-gray-600 text-sm">{internalData.record_time}</Text>
<LucideIcon icon="ChevronDown" size={12} color="#aaa" clickable={false} />
</Pressable>
<Text className="text-3xl text-blue-700 text-right">{internalData.amount}</Text>
</View>
<Text className="text-gray-400 text-right">{keydownInput}</Text>
<AInput
value={internalData.remark}
placeholder="备注"
className="bg-[#f2f3f5] border-0"
size="sm"
onChangeText={val => handleChange({ remark: val })}
onFocus={() => setRemarkIsFocused(true)}
onBlur={() => setRemarkIsFocused(false)}
/>
</View>
</TouchableWithoutFeedback>
<View className="flex flex-row flex-wrap" style={{ height: 220 }}>
{keys.map((item, idx) => {
const isFirstCol = idx % 4 === 0
const isFirstRow = idx < 4
const isLastCol = (idx + 1) % 4 === 0
const isLastRow = idx >= 12
return (
<Pressable
key={item.key}
onPress={() => handlePress(item.key)}
style={{
width: '25%',
height: '25%',
justifyContent: 'center',
alignItems: 'center',
borderTopWidth: isFirstRow ? 0.3 : 0,
borderLeftWidth: isFirstCol ? 0.3 : 0,
borderRightWidth: !isLastCol ? 0.3 : 0,
borderBottomWidth: !isLastRow ? 0.3 : 0,
borderColor: '#ddd',
}}
>
{({ pressed }) => {
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
return (
<>
<View
style={{
...StyleSheet.absoluteFillObject,
backgroundColor: pressed ? pressedBg : defaultBg,
}}
/>
{item.content}
</>
)
}}
</Pressable>
)
})}
</View>
{remarkIsFocused && keyboardHeight > 220 && (
<View style={{ height: keyboardHeight + 10 - 220 }} />
)}
<ADatePicker
pickerTitle="记账时间"
mode="date"
date={internalData.record_time}
show={showDatePicker}
onClose={() => {
setShowDatePicker(false)
}}
onConfirm={(date) => {
setShowDatePicker(false)
handleChange({ record_time: date })
}}
/>
</View>
)
}
@@ -0,0 +1,89 @@
import type { ViewStyle } from 'react-native'
import type { MoneyCategoryItem } from '@/types/money/category'
import React, { useMemo } from 'react'
import { FlatList, Pressable, Text, View } from 'react-native'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
interface CategoryGridProps {
data: MoneyCategoryItem[]
selectedCategory?: MoneyCategoryItem | null
onSelectCategory?: (item: MoneyCategoryItem | null) => void
className?: string
style?: ViewStyle
}
export default function CategoryGrid({
data,
selectedCategory,
onSelectCategory,
className,
style,
}: CategoryGridProps) {
// 计算补齐数组,让每行4个
const memoItems = useMemo(() => {
const items = [...data]
const remainder = items.length % 4
if (remainder > 0) {
const emptyCount = 4 - remainder
for (let i = 0; i < emptyCount; i++) {
items.push({ id: `empty-${i}` } as any)
}
}
return items
}, [data])
return (
<FlatList
data={memoItems}
numColumns={4}
keyExtractor={item => item.id.toString()}
showsVerticalScrollIndicator={false}
className={`${className}`}
style={[style]}
renderItem={({ item }) => {
if (!('name' in item)) {
return <View style={{ flex: 1 / 4, margin: 4 }} />
}
const isSelected = selectedCategory?.id === item.id
return (
<Pressable
style={{ flex: 1 / 4, margin: 4, alignItems: 'center' }}
onPress={() => onSelectCategory?.(item)}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
marginTop: 4,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: isSelected ? '#333' : '#F3F4F6',
}}
>
<MoneyCategoryIcon
name={item.icon}
size={24}
color={
isSelected ? '#fff' : '#000'
}
/>
</View>
<Text
style={{
marginBottom: 4,
fontSize: 14,
textAlign: 'center',
}}
>
{item.name}
</Text>
</Pressable>
)
}}
/>
)
}
@@ -0,0 +1,228 @@
import type { MoneyCategoryItem } from '@/types/money/category'
import type { MoneyRecordBase, MoneyRecordItem } from '@/types/money/record'
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, updateMoneyRecordApi } 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 moneyCategoryStore = useMoneyCategoryStore()
const moneyRecordStore = useMoneyRecordStore()
const router = useRouter()
const expense = moneyCategoryStore.getExpenseCategories()
const income = moneyCategoryStore.getIncomeCategories()
const { id: updateId } = useLocalSearchParams<{ id: string }>()
// 自定义键盘
const [showKeydown, setShowKeydown] = useState(false)
// 选中分类
const [selectedCategory, setSelectedCategory] = useState<MoneyCategoryItem | null>(null)
// 记账记录
const defaultMoneyRecord: MoneyRecordBase = {
id: -1,
category_id: -1,
username: '',
category_icon: '',
category_name: '',
category_type: 0,
user_id: -1,
amount: '0.00',
remark: '',
record_time: getCurrentDate(),
create_time: '',
}
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) {
toast.show({
title: '请选择分类',
})
return
}
if (moneyRecord.amount === '0.00') {
toast.show({
title: '请输入金额',
})
return
}
const amount = moneyRecord.amount.replace(/^-+/, '')
if (updateId) {
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 = {
...moneyRecord,
category_id: selectedCategory.id,
category_type: selectedCategory.type,
category_name: selectedCategory.name,
category_icon: selectedCategory.icon,
}
moneyRecordStore.updateMoneyRecord(result)
router.back()
}
}).catch((err) => {
console.log(err)
})
}
else {
insertMoneyRecordApi({
amount,
category_id: selectedCategory.id,
remark: moneyRecord.remark,
record_time: moneyRecord.record_time,
})
.then((res) => {
if (res.isSuccess) {
const result: MoneyRecordItem = {
...res.data,
category_type: selectedCategory.type,
category_name: selectedCategory.name,
category_icon: selectedCategory.icon,
}
moneyRecordStore.addMoneyRecord(result)
router.back()
}
else {
toast.show({
title: res.message || '保存失败',
})
}
})
.catch((err) => {
console.log('发生异常啦')
console.log(err)
})
}
}
return (
<View className="flex-1">
<CustomHeader
showHeader={false}
screenProps={{
options: {
animation: 'slide_from_bottom',
},
}}
/>
<PagerTabs
selectedIndex={selectedCategory?.type}
left={(
<TouchableOpacity
className="w-[30%] flex items-center px-4 flex-row"
onPress={() => {
router.navigate('/money/category/setting')
}}
>
<LucideIcon icon="LayoutList" />
<Text className="ml-1">管理</Text>
</TouchableOpacity>
)}
right={(
<TouchableOpacity
className="w-[30%] flex flex-row items-center px-4"
onPress={() => {
router.back()
}}
>
<Text className="ml-auto text-base text-gray-400">取消</Text>
</TouchableOpacity>
)}
routes={[
{
key: 'expense',
title: '支出',
element: (
<CategoryGrid
data={expense}
selectedCategory={selectedCategory}
onSelectCategory={(data) => {
setShowKeydown(true)
setSelectedCategory(data)
}}
className="mt-4"
/>
),
},
{
key: 'income',
title: '收入',
element: (
<CategoryGrid
data={income}
selectedCategory={selectedCategory}
onSelectCategory={(data) => {
setShowKeydown(true)
setSelectedCategory(data)
}}
className="mt-4"
/>
),
},
]}
/>
<AmountKeyboard
show={showKeydown}
record_time={moneyRecord.record_time}
amount={moneyRecord.amount}
remark={moneyRecord.remark}
onChange={(data) => {
setMoneyRecord({
...moneyRecord,
record_time: data.record_time,
amount: data.amount,
remark: data.remark,
})
}}
onDone={handleSaveRecord}
/>
</View>
)
}