u
This commit is contained in:
1 parent
44ab5416a3
commit
1935906676
17 files changed
+322
-266
No files matched your search
+56
-18
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { AButton } from '@/components/common/ao'
|
||||
import { AButton, AInput } from '@/components/common/ao'
|
||||
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
|
||||
import {
|
||||
Drawer,
|
||||
@@ -10,34 +10,38 @@ import {
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
} from '@/components/ui/drawer'
|
||||
import { Input, InputField } from '@/components/ui/input'
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
import { MoneyCategoryEnum } from '@/types/money/category'
|
||||
|
||||
interface CreateCategoryProps {
|
||||
export interface EditCategoryForm {
|
||||
id?: number
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
interface EditCategoryProps {
|
||||
open: boolean
|
||||
onClose?: () => void
|
||||
type: MoneyCategoryEnum
|
||||
}
|
||||
|
||||
interface CreateCategoryForm {
|
||||
icon: string
|
||||
name: string
|
||||
onConfirm?: (form: EditCategoryForm) => Promise<void>
|
||||
}
|
||||
|
||||
export default function CreateCategory({
|
||||
open,
|
||||
onClose,
|
||||
type,
|
||||
}: CreateCategoryProps) {
|
||||
onConfirm,
|
||||
}: EditCategoryProps) {
|
||||
const moneyCategoryStore = useMoneyCategoryStore()
|
||||
|
||||
const [isOpen, setIsOpen] = useState(open)
|
||||
|
||||
const [form, setForm] = useState<CreateCategoryForm>({
|
||||
const [form, setForm] = useState<EditCategoryForm>({
|
||||
icon: '',
|
||||
name: '',
|
||||
})
|
||||
|
||||
const [confirmDisabled, setConfirmDisabled] = useState(true)
|
||||
const [confirmLoading, setConfirmLoading] = useState(false)
|
||||
|
||||
@@ -46,13 +50,13 @@ export default function CreateCategory({
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
if (form.name && form.name.length < 6) {
|
||||
if (form.name && form.name.length <= 5 && form.icon) {
|
||||
setConfirmDisabled(false)
|
||||
}
|
||||
else {
|
||||
setConfirmDisabled(true)
|
||||
}
|
||||
}, [form.name])
|
||||
}, [form.name, form.icon])
|
||||
|
||||
function handleClose() {
|
||||
setIsOpen(false)
|
||||
@@ -79,15 +83,49 @@ export default function CreateCategory({
|
||||
|
||||
<Text className="text-lg font-medium">{type === MoneyCategoryEnum.Expense ? '添加支出分类' : '添加收入分类'}</Text>
|
||||
|
||||
<AButton size="xs" disabled={confirmDisabled} loading={confirmLoading}>确认</AButton>
|
||||
<AButton
|
||||
size="xs"
|
||||
disabled={confirmDisabled}
|
||||
loading={confirmLoading}
|
||||
onPress={async () => {
|
||||
if (!onConfirm) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setConfirmLoading(true)
|
||||
|
||||
const trimmedForm = {
|
||||
...form,
|
||||
name: form.name.trim(),
|
||||
}
|
||||
|
||||
await onConfirm(trimmedForm)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
// 外部报错时,保持弹窗打开,loading 结束
|
||||
}
|
||||
finally {
|
||||
setConfirmLoading(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
确认
|
||||
</AButton>
|
||||
</View>
|
||||
|
||||
<Input
|
||||
variant="outline"
|
||||
>
|
||||
<InputField placeholder="请输入名称(不超过5个字符)" />
|
||||
</Input>
|
||||
|
||||
<AInput
|
||||
placeholder="请输入名称(不超过5个字符)"
|
||||
onChangeText={(text) => {
|
||||
setForm((val) => {
|
||||
return {
|
||||
...val,
|
||||
name: text,
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</DrawerHeader>
|
||||
<DrawerBody className="mb-16">
|
||||
{Object.entries(moneyCategoryStore.ICON_GROUPS).map(([groupName, icons]) => (
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { SortableGridRenderItem } from 'react-native-sortables'
|
||||
import type { EditCategoryForm } from './components/EditCategory'
|
||||
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Pressable, Text, Vibration, View } from 'react-native'
|
||||
|
||||
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'
|
||||
@@ -10,7 +11,8 @@ import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
import { MoneyCategoryEnum } from '@/types/money/category'
|
||||
import CreateCategory from './components/CreateCategory'
|
||||
import { toast } from '@/utils/toast'
|
||||
import EditCategory from './components/EditCategory'
|
||||
|
||||
export default function MoneyCategorySetting() {
|
||||
const [selectedTab, setSelectedTab] = useState(MoneyCategoryEnum.Expense)
|
||||
@@ -27,7 +29,7 @@ export default function MoneyCategorySetting() {
|
||||
|
||||
useEffect(() => {
|
||||
setCategories(selectedTab === MoneyCategoryEnum.Expense ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
|
||||
}, [selectedTab])
|
||||
}, [selectedTab, moneyCategoryStore.categories])
|
||||
|
||||
const renderItem = useCallback<SortableGridRenderItem<MoneyCategoryItem>>(
|
||||
({ item }) => (
|
||||
@@ -61,6 +63,42 @@ export default function MoneyCategorySetting() {
|
||||
[],
|
||||
)
|
||||
|
||||
async function handleConfirmEdit(form: EditCategoryForm) {
|
||||
const isDuplicate = moneyCategoryStore.isDuplicateCategory(form.name, selectedTab)
|
||||
|
||||
if (isDuplicate) {
|
||||
toast.show({
|
||||
type: 'warning',
|
||||
title: '类别不能重复',
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await moneyCategoryStore.createCategory({
|
||||
id: 0,
|
||||
name: form.name,
|
||||
icon: form.icon,
|
||||
sort_number: 0,
|
||||
type: selectedTab,
|
||||
})
|
||||
|
||||
setOpenCreateCategory(false)
|
||||
}
|
||||
catch (err) {
|
||||
const message
|
||||
= err instanceof Error
|
||||
? err.message
|
||||
: '新增失败,请稍后再试'
|
||||
|
||||
toast.show({
|
||||
type: 'error',
|
||||
title: message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<CustomHeader
|
||||
@@ -146,7 +184,6 @@ export default function MoneyCategorySetting() {
|
||||
// 设置 1 秒后删除
|
||||
const timer = setTimeout(() => {
|
||||
const deleteId = Number(draggingId)
|
||||
setCategories(prev => prev.filter(c => c.id !== deleteId))
|
||||
|
||||
moneyCategoryStore.deleteCategoryById(deleteId)
|
||||
|
||||
@@ -189,10 +226,11 @@ export default function MoneyCategorySetting() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<CreateCategory
|
||||
<EditCategory
|
||||
type={selectedTab}
|
||||
open={openCreateCategory}
|
||||
onClose={() => setOpenCreateCategory(false)}
|
||||
onConfirm={handleConfirmEdit}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -83,6 +83,21 @@ export default function AmountKeyboard({
|
||||
})
|
||||
}, [record_time, amount, remark])
|
||||
|
||||
useEffect(() => {
|
||||
if (!keydownInput) {
|
||||
handleChange({ amount: '0' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const computed = calculateWithBig(keydownInput)
|
||||
handleChange({ amount: computed })
|
||||
}
|
||||
catch {
|
||||
handleChange({ amount: '0' })
|
||||
}
|
||||
}, [keydownInput])
|
||||
|
||||
const handleChange = (newData: Partial<typeof internalData>) => {
|
||||
const updated = { ...internalData, ...newData }
|
||||
setInternalData(updated)
|
||||
@@ -98,9 +113,11 @@ export default function AmountKeyboard({
|
||||
newInput = prev.slice(0, -1)
|
||||
}
|
||||
else if (key === 'clear') {
|
||||
const currentDate = getCurrentDate()
|
||||
handleChange({ record_time: currentDate, amount: '', remark: '' })
|
||||
|
||||
setInternalData({
|
||||
record_time: getCurrentDate(),
|
||||
amount: '0',
|
||||
remark: '',
|
||||
})
|
||||
return ''
|
||||
}
|
||||
else if (key === '/') {
|
||||
@@ -189,16 +206,7 @@ export default function AmountKeyboard({
|
||||
newInput = prev + keyStr
|
||||
}
|
||||
}
|
||||
|
||||
// 计算结果
|
||||
try {
|
||||
const computed = calculateWithBig(newInput)
|
||||
handleChange({ amount: computed })
|
||||
}
|
||||
catch (e) {
|
||||
handleChange({ amount: '0' })
|
||||
}
|
||||
|
||||
|
||||
return newInput
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function CategoryGrid({
|
||||
>
|
||||
<MoneyCategoryIcon
|
||||
name={item.icon}
|
||||
size={24}
|
||||
size={28}
|
||||
color={
|
||||
isSelected ? '#fff' : '#000'
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ export default function MoenyRecordEditor() {
|
||||
if (res.isSuccess) {
|
||||
const result: MoneyRecordItem = {
|
||||
...res.data,
|
||||
category_id: selectedCategory.id,
|
||||
category_type: selectedCategory.type,
|
||||
category_name: selectedCategory.name,
|
||||
category_icon: selectedCategory.icon,
|
||||
|
||||
Reference in new issue
Block a user