diff --git a/code/allapp/allapp-go/biz/handler/money/money_category_service.go b/code/allapp/allapp-go/biz/handler/money/money_category_service.go
index 76110edc..609ffdbf 100644
--- a/code/allapp/allapp-go/biz/handler/money/money_category_service.go
+++ b/code/allapp/allapp-go/biz/handler/money/money_category_service.go
@@ -10,6 +10,7 @@ import (
"allapp/utils/idgen"
"allapp/utils/response"
"context"
+ "fmt"
money "allapp/biz/model/money"
@@ -123,8 +124,11 @@ func UpdateMoneyCategorySort(ctx context.Context, c *app.RequestContext) {
func CreateMoneyCategory(ctx context.Context, c *app.RequestContext) {
var err error
var req money.CreateMoneyCategoryReq
+
err = c.BindAndValidate(&req)
if err != nil {
+ fmt.Println(err)
+
c.String(consts.StatusBadRequest, err.Error())
return
}
@@ -134,13 +138,12 @@ func CreateMoneyCategory(ctx context.Context, c *app.RequestContext) {
ID := idgen.NextId()
params := repo.InsertMoneyUserCategoryParams{
- ID: ID,
- UserID: middleware.GetUserID(ctx),
- Name: req.Name,
- Icon: req.Icon,
- Type: req.Type,
- // 默认排序号为ID
- SortNumber: 0,
+ ID: ID,
+ UserID: middleware.GetUserID(ctx),
+ Name: req.Name,
+ Icon: req.Icon,
+ Type: req.Type,
+ SortNumber: req.SortNumber,
}
// 插入
diff --git a/code/allapp/allapp-go/biz/model/money/money_category_types.pb.go b/code/allapp/allapp-go/biz/model/money/money_category_types.pb.go
index dfa944d3..d7a7f90b 100644
--- a/code/allapp/allapp-go/biz/model/money/money_category_types.pb.go
+++ b/code/allapp/allapp-go/biz/model/money/money_category_types.pb.go
@@ -423,9 +423,10 @@ type CreateMoneyCategoryReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Name string `protobuf:"bytes,1,opt,name=Name,proto3" form:"name" json:"name" vd:"$ != '' && len($) <= 5"`
- Icon string `protobuf:"bytes,2,opt,name=Icon,proto3" form:"icon" json:"icon" vd:"$ != ''"`
- Type int32 `protobuf:"varint,3,opt,name=Type,proto3" form:"type" json:"type"`
+ Name string `protobuf:"bytes,1,opt,name=Name,proto3" form:"name" json:"name" vd:"$ != ''"`
+ Icon string `protobuf:"bytes,2,opt,name=Icon,proto3" form:"icon" json:"icon" vd:"$ != ''"`
+ Type int32 `protobuf:"varint,3,opt,name=Type,proto3" form:"type" json:"type"`
+ SortNumber int32 `protobuf:"varint,4,opt,name=SortNumber,proto3" form:"sort_number" json:"sort_number"`
}
func (x *CreateMoneyCategoryReq) Reset() {
@@ -481,6 +482,13 @@ func (x *CreateMoneyCategoryReq) GetType() int32 {
return 0
}
+func (x *CreateMoneyCategoryReq) GetSortNumber() int32 {
+ if x != nil {
+ return x.SortNumber
+ }
+ return 0
+}
+
type CreateMoneyCategoryResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -572,22 +580,24 @@ var file_money_money_category_types_proto_rawDesc = []byte{
0x74, 0x65, 0x6d, 0x42, 0x08, 0xca, 0xbb, 0x18, 0x04, 0x64, 0x61, 0x74, 0x61, 0x52, 0x04, 0x44,
0x61, 0x74, 0x61, 0x22, 0x1d, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e,
0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x65,
- 0x73, 0x70, 0x22, 0x97, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e,
- 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x12, 0x36, 0x0a,
- 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xca, 0xbb, 0x18,
- 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xda, 0xbb, 0x18, 0x16, 0x24, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27,
- 0x20, 0x26, 0x26, 0x20, 0x6c, 0x65, 0x6e, 0x28, 0x24, 0x29, 0x20, 0x3c, 0x3d, 0x20, 0x35, 0x52,
- 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x49, 0x63, 0x6f, 0x6e, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x09, 0x42, 0x13, 0xca, 0xbb, 0x18, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0xda, 0xbb, 0x18,
- 0x07, 0x24, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x52, 0x04, 0x49, 0x63, 0x6f, 0x6e, 0x12, 0x1c,
- 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x08, 0xca, 0xbb,
- 0x18, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x17,
- 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67,
- 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x03, 0x42, 0x06, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0x52, 0x02, 0x49, 0x64, 0x42,
- 0x18, 0x5a, 0x16, 0x61, 0x6c, 0x6c, 0x61, 0x70, 0x70, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f,
- 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x33,
+ 0x73, 0x70, 0x22, 0xb9, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e,
+ 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x12, 0x27, 0x0a,
+ 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x13, 0xca, 0xbb, 0x18,
+ 0x04, 0x6e, 0x61, 0x6d, 0x65, 0xda, 0xbb, 0x18, 0x07, 0x24, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27,
+ 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x49, 0x63, 0x6f, 0x6e, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x09, 0x42, 0x13, 0xca, 0xbb, 0x18, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0xda, 0xbb,
+ 0x18, 0x07, 0x24, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x52, 0x04, 0x49, 0x63, 0x6f, 0x6e, 0x12,
+ 0x1c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x08, 0xca,
+ 0xbb, 0x18, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a,
+ 0x0a, 0x53, 0x6f, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
+ 0x05, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62,
+ 0x65, 0x72, 0x52, 0x0a, 0x53, 0x6f, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x31,
+ 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74,
+ 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x02, 0x49, 0x64, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x06, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0x52, 0x02, 0x49,
+ 0x64, 0x42, 0x18, 0x5a, 0x16, 0x61, 0x6c, 0x6c, 0x61, 0x70, 0x70, 0x2f, 0x62, 0x69, 0x7a, 0x2f,
+ 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x33,
}
var (
diff --git a/code/allapp/allapp-go/db/queries/money_record.sql b/code/allapp/allapp-go/db/queries/money_record.sql
index 15b92082..8e0bf310 100644
--- a/code/allapp/allapp-go/db/queries/money_record.sql
+++ b/code/allapp/allapp-go/db/queries/money_record.sql
@@ -31,9 +31,11 @@ INSERT INTO money_record (
user_id,
amount,
remark,
- record_time
+ record_time,
+ create_time,
+ update_time
) VALUES (
- $1, $2, $3, $4, $5,$6
+ $1, $2, $3, $4, $5,$6,NOW(),NOW()
)
RETURNING *;
diff --git a/code/allapp/allapp-go/db/repo/money_record.sql.go b/code/allapp/allapp-go/db/repo/money_record.sql.go
index 190976a6..c52f4821 100644
--- a/code/allapp/allapp-go/db/repo/money_record.sql.go
+++ b/code/allapp/allapp-go/db/repo/money_record.sql.go
@@ -181,9 +181,11 @@ INSERT INTO money_record (
user_id,
amount,
remark,
- record_time
+ record_time,
+ create_time,
+ update_time
) VALUES (
- $1, $2, $3, $4, $5,$6
+ $1, $2, $3, $4, $5,$6,NOW(),NOW()
)
RETURNING id, category_id, user_id, amount, remark, record_time, create_time, update_time
`
diff --git a/code/allapp/allapp-go/idl/money/money_category_types.proto b/code/allapp/allapp-go/idl/money/money_category_types.proto
index 16e6abb8..c01ada2b 100644
--- a/code/allapp/allapp-go/idl/money/money_category_types.proto
+++ b/code/allapp/allapp-go/idl/money/money_category_types.proto
@@ -47,13 +47,14 @@ message UpdateMoneyCategorySortResp {}
message CreateMoneyCategoryReq {
string Name = 1 [
(api.body)="name",
- (api.vd) = "$ != '' && len($) <= 5"
+ (api.vd) = "$ != ''"
];
string Icon = 2 [
(api.body)="icon",
(api.vd) = "$ != ''"
];
int32 Type = 3 [(api.body)="type"];
+ int32 SortNumber = 4 [(api.body)="sort_number"];
}
message CreateMoneyCategoryResp {
diff --git a/code/allapp/allapp-rn/src/app/(tabs)/money/components/TransactionsList.tsx b/code/allapp/allapp-rn/src/app/(tabs)/money/components/TransactionsList.tsx
index 173ce458..6baed1fe 100644
--- a/code/allapp/allapp-rn/src/app/(tabs)/money/components/TransactionsList.tsx
+++ b/code/allapp/allapp-rn/src/app/(tabs)/money/components/TransactionsList.tsx
@@ -146,7 +146,7 @@ export default function StaticTransactions({
}}
>
-
+
diff --git a/code/allapp/allapp-rn/src/app/(tabs)/money/index.tsx b/code/allapp/allapp-rn/src/app/(tabs)/money/index.tsx
index 9965583a..00ebcf83 100644
--- a/code/allapp/allapp-rn/src/app/(tabs)/money/index.tsx
+++ b/code/allapp/allapp-rn/src/app/(tabs)/money/index.tsx
@@ -34,8 +34,7 @@ export default function Money() {
record_time: searchParams.record_time,
}).then((res) => {
if (res.isSuccess) {
- moneyRecordStore.setRecords(res.data.records, res.data.total_expense, res.data.total_income, res.data.total_balance, searchParams.record_time,
- )
+ moneyRecordStore.setRecords(res.data.records, searchParams.record_time)
}
else {
toast.show({
@@ -143,7 +142,7 @@ export default function Money() {
numberOfLines={1}
ellipsizeMode="tail"
>
- {moneyRecordStore.total_income}
+ {moneyRecordStore.getTotalIncome()}
@@ -154,7 +153,7 @@ export default function Money() {
numberOfLines={1}
ellipsizeMode="tail"
>
- {moneyRecordStore.total_expense}
+ {moneyRecordStore.getTotalExpense()}
@@ -165,7 +164,7 @@ export default function Money() {
numberOfLines={1}
ellipsizeMode="tail"
>
- {moneyRecordStore.total_balance}
+ {moneyRecordStore.getTotalBalance()}
diff --git a/code/allapp/allapp-rn/src/app/money/category/setting/components/CreateCategory.tsx b/code/allapp/allapp-rn/src/app/money/category/setting/components/EditCategory.tsx
similarity index 69%
rename from code/allapp/allapp-rn/src/app/money/category/setting/components/CreateCategory.tsx
rename to code/allapp/allapp-rn/src/app/money/category/setting/components/EditCategory.tsx
index 07bffe15..68a8c2a6 100644
--- a/code/allapp/allapp-rn/src/app/money/category/setting/components/CreateCategory.tsx
+++ b/code/allapp/allapp-rn/src/app/money/category/setting/components/EditCategory.tsx
@@ -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
}
export default function CreateCategory({
open,
onClose,
type,
-}: CreateCategoryProps) {
+ onConfirm,
+}: EditCategoryProps) {
const moneyCategoryStore = useMoneyCategoryStore()
const [isOpen, setIsOpen] = useState(open)
- const [form, setForm] = useState({
+ const [form, setForm] = useState({
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({
{type === MoneyCategoryEnum.Expense ? '添加支出分类' : '添加收入分类'}
- 确认
+ {
+ 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)
+ }
+ }}
+ >
+ 确认
+
-
-
-
-
+ {
+ setForm((val) => {
+ return {
+ ...val,
+ name: text,
+ }
+ })
+ }}
+ />
{Object.entries(moneyCategoryStore.ICON_GROUPS).map(([groupName, icons]) => (
diff --git a/code/allapp/allapp-rn/src/app/money/category/setting/index.tsx b/code/allapp/allapp-rn/src/app/money/category/setting/index.tsx
index e880851d..cdbc1b24 100644
--- a/code/allapp/allapp-rn/src/app/money/category/setting/index.tsx
+++ b/code/allapp/allapp-rn/src/app/money/category/setting/index.tsx
@@ -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>(
({ 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 (
{
const deleteId = Number(draggingId)
- setCategories(prev => prev.filter(c => c.id !== deleteId))
moneyCategoryStore.deleteCategoryById(deleteId)
@@ -189,10 +226,11 @@ export default function MoneyCategorySetting() {
- setOpenCreateCategory(false)}
+ onConfirm={handleConfirmEdit}
/>
)
diff --git a/code/allapp/allapp-rn/src/app/money/record/editor/components/AmountKeyboard.tsx b/code/allapp/allapp-rn/src/app/money/record/editor/components/AmountKeyboard.tsx
index 7773994c..20e03858 100644
--- a/code/allapp/allapp-rn/src/app/money/record/editor/components/AmountKeyboard.tsx
+++ b/code/allapp/allapp-rn/src/app/money/record/editor/components/AmountKeyboard.tsx
@@ -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) => {
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
})
}
diff --git a/code/allapp/allapp-rn/src/app/money/record/editor/components/CategoryGrid.tsx b/code/allapp/allapp-rn/src/app/money/record/editor/components/CategoryGrid.tsx
index b5559a23..f2301c61 100644
--- a/code/allapp/allapp-rn/src/app/money/record/editor/components/CategoryGrid.tsx
+++ b/code/allapp/allapp-rn/src/app/money/record/editor/components/CategoryGrid.tsx
@@ -66,7 +66,7 @@ export default function CategoryGrid({
>
>(`${API_PREFIX}/updateSort`, params)
}
+
+export function createMoneyCategoryApi(params: CreateMoneyCategoryParams) {
+ return alovaInstance.Post>(`${API_PREFIX}/create`, params)
+}
diff --git a/code/allapp/allapp-rn/src/request/index.ts b/code/allapp/allapp-rn/src/request/index.ts
index de7e4c95..476eb11d 100644
--- a/code/allapp/allapp-rn/src/request/index.ts
+++ b/code/allapp/allapp-rn/src/request/index.ts
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
- baseURL: 'http://192.168.5.6:8086/app',
- // baseURL: 'http://192.168.1.137:8086/app',
+ // baseURL: 'http://192.168.5.6:8086/app',
+ baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
diff --git a/code/allapp/allapp-rn/src/store/moneyCategory.ts b/code/allapp/allapp-rn/src/store/moneyCategory.ts
index 3d27bf67..d0b48ef2 100644
--- a/code/allapp/allapp-rn/src/store/moneyCategory.ts
+++ b/code/allapp/allapp-rn/src/store/moneyCategory.ts
@@ -1,6 +1,7 @@
import type { MoneyCategoryItem, UpdateMoneyCategorySortItem } from '@/types/money/category'
import { create } from 'zustand'
-import { deleteMoneyCategoryApi, queryMoneyCategoryApi, updateMoneyCategorySortApi } from '@/request/api/money/category'
+import { createMoneyCategoryApi, deleteMoneyCategoryApi, queryMoneyCategoryApi, updateMoneyCategorySortApi } from '@/request/api/money/category'
+import { MoneyCategoryEnum } from '@/types/money/category'
interface MoneyCategoryStore {
ICON_GROUPS: Record
@@ -8,15 +9,17 @@ interface MoneyCategoryStore {
init: () => Promise
setCategories: (categories: MoneyCategoryItem[]) => void
- addCategory: (category: MoneyCategoryItem) => void
getExpenseCategories: () => MoneyCategoryItem[]
getIncomeCategories: () => MoneyCategoryItem[]
getCategoryById: (id: number) => MoneyCategoryItem | null
- updateSortNumbers: (data: MoneyCategoryItem[], type: number) => void
- deleteCategoryById: (id: number) => void
+ createCategory: (category: MoneyCategoryItem) => Promise
+ updateSortNumbers: (data: MoneyCategoryItem[], type: number) => Promise
+ deleteCategoryById: (id: number) => Promise
+
+ isDuplicateCategory: (name: string, type: number) => boolean
clear: () => void
}
@@ -94,9 +97,6 @@ export const useMoneyCategoryStore = create()(
setCategories: categories => set({ categories }),
- addCategory: category =>
- set({ categories: [...get().categories, category] }),
-
init: async () => {
try {
await queryMoneyCategoryApi().then((res) => {
@@ -118,63 +118,98 @@ export const useMoneyCategoryStore = create()(
set({ categories: [] })
},
- getExpenseCategories: () => (get().categories || []).filter(c => c.type === 0),
- getIncomeCategories: () => (get().categories || []).filter(c => c.type === 1),
+ getExpenseCategories: () =>
+ (get().categories || [])
+ .filter(c => c.type === MoneyCategoryEnum.Expense)
+ .sort((a, b) => (a.sort_number ?? 0) - (b.sort_number ?? 0)),
+
+ getIncomeCategories: () =>
+ (get().categories || [])
+ .filter(c => c.type === MoneyCategoryEnum.Income)
+ .sort((a, b) => (a.sort_number ?? 0) - (b.sort_number ?? 0)),
getCategoryById: (id: number) => {
return get().categories.find(c => c.id === id) || null
},
- updateSortNumbers: (data, type) => {
- // 获取当前所有分类
- const allCategories = get().categories
+ createCategory: async (category) => {
+ const categories = category.type === MoneyCategoryEnum.Expense ? get().getExpenseCategories() : get().getIncomeCategories()
- // 保留另一类分类
- const otherCategories = allCategories.filter(c => c.type !== type)
+ const maxSortNumber = categories[categories.length - 1].sort_number + 1 || 1
- // 给当前类型的数据重新计算 sort_number
- const updatedData = data.map((item, index) => ({
- ...item,
- sort_number: index, // 按顺序重新赋值
- }))
+ try {
+ const res = await createMoneyCategoryApi({
+ name: category.name,
+ icon: category.icon,
+ type: category.type,
+ sort_number: maxSortNumber,
+ })
- // 合并另一类分类
- const finalCategories = [...otherCategories, ...updatedData]
+ if (!res.isSuccess) {
+ throw new Error(res.message)
+ }
- // 构造后端请求 payload,只需要 id 和 sort_number
- const payload: UpdateMoneyCategorySortItem[] = updatedData.map(item => ({
+ category.id = res.data.id
+ category.sort_number = maxSortNumber
+
+ set({
+ categories: [...get().categories, category],
+ })
+ }
+ catch (err) {
+ console.error(err)
+ throw err
+ }
+ },
+
+ updateSortNumbers: async (data, type) => {
+ const payload = data.map((item, index) => ({
id: item.id,
- sort_number: item.sort_number,
+ sort_number: index,
}))
- // 调用后端接口更新排序
- updateMoneyCategorySortApi({
- data: payload,
- }).then((res) => {
- console.log('后端返回:', res)
- }).catch((err) => {
- console.log(err)
- })
+ try {
+ const res = await updateMoneyCategorySortApi({ data: payload })
+ if (!res.isSuccess) {
+ throw new Error(res.message)
+ }
- // 更新前端 state
- set({ categories: finalCategories })
+ // 更新前端 state
+ const allCategories = get().categories
+ const otherCategories = allCategories.filter(c => c.type !== type)
+ const updatedData = data.map((item, index) => ({
+ ...item,
+ sort_number: index,
+ }))
+
+ set({ categories: [...otherCategories, ...updatedData] })
+ }
+ catch (err) {
+ throw err
+ }
},
deleteCategoryById: async (id: number) => {
- const allCategories = get().categories
+ try {
+ const res = await deleteMoneyCategoryApi({ id })
+ if (!res.isSuccess) {
+ throw new Error(res.message)
+ }
- // 删除指定 id,同时只处理同类型的数据
- const finalCategories = allCategories
- .filter(c => c.id !== id)
+ // 更新 state
+ const allCategories = get().categories
+ const finalCategories = allCategories.filter(c => c.id !== id)
+ set({ categories: finalCategories })
+ }
+ catch (err) {
+ console.error(err)
+ throw err
+ }
+ },
- // 更新 state
- set({ categories: finalCategories })
-
- // 调用删除接口
- await deleteMoneyCategoryApi({ id }).catch((err) => {
- console.log(err)
- })
+ isDuplicateCategory: (name, type) => {
+ const categories = get().categories || []
+ return categories.some(c => c.name === name && c.type === type)
},
}),
-
)
diff --git a/code/allapp/allapp-rn/src/store/moneyRecord.ts b/code/allapp/allapp-rn/src/store/moneyRecord.ts
index 4f07e3b6..c0edfa1a 100644
--- a/code/allapp/allapp-rn/src/store/moneyRecord.ts
+++ b/code/allapp/allapp-rn/src/store/moneyRecord.ts
@@ -1,19 +1,20 @@
import type { MoneyRecordGroup, MoneyRecordItem } from '@/types/money/record'
import { create } from 'zustand'
import { deleteMoneyRecordApi } from '@/request/api/money/record'
+import { MoneyCategoryEnum } from '@/types/money/category'
import { add, sub } from '@/utils/math'
import { isInMonth } from '@/utils/time'
-import { useMoneyCategoryStore } from './moneyCategory'
interface MoneyRecordStore {
records: MoneyRecordGroup[]
currentMonth: string
- total_income: string
- total_expense: string
- total_balance: string
+ /* computed */
+ getTotalIncome: () => string
+ getTotalExpense: () => string
+ getTotalBalance: () => string
- setRecords: (records: MoneyRecordGroup[], total_expense: string, total_income: string, total_balance: string, month: string) => void
+ setRecords: (records: MoneyRecordGroup[], month: string) => void
setCurrentMonth: (month: string) => void
addMoneyRecord: (data: MoneyRecordItem) => void
@@ -23,164 +24,77 @@ interface MoneyRecordStore {
getMoneyRecordById: (id: number) => MoneyRecordItem | null
}
+function recomputeGroups(records: MoneyRecordItem[]) {
+ const groupsMap: Record = {}
+
+ records.forEach((r) => {
+ const group = groupsMap[r.record_time] ?? { record_time: r.record_time, records: [], income: '0.00', expense: '0.00' }
+ group.records.push(r)
+ if (r.category_type === MoneyCategoryEnum.Expense)
+ group.expense = add(group.expense, r.amount)
+ if (r.category_type === MoneyCategoryEnum.Income)
+ group.income = add(group.income, r.amount)
+ groupsMap[r.record_time] = group
+ })
+
+ return Object.values(groupsMap).sort((a, b) => b.record_time.localeCompare(a.record_time))
+}
+
export const useMoneyRecordStore = create((set, get) => ({
records: [],
currentMonth: '',
- total_income: '0.00',
- total_expense: '0.00',
- total_balance: '0.00',
+ /* ---------------- computed ---------------- */
+ getTotalIncome: () => {
+ return get().records.reduce((sum, g) => add(sum, g.income), '0.00')
+ },
- setRecords: (records, total_expense, total_income, total_balance, month) => {
- set({ records, currentMonth: month, total_income, total_expense, total_balance })
+ getTotalExpense: () => {
+ return get().records.reduce((sum, g) => add(sum, g.expense), '0.00')
+ },
+
+ getTotalBalance: () => {
+ return sub(get().getTotalIncome(), get().getTotalExpense())
+ },
+
+ /* ---------------- base ---------------- */
+ setRecords: (records, month) => {
+ const flatRecords = records.flatMap(g => g.records)
+ const newGroups = recomputeGroups(flatRecords)
+ set({ records: newGroups, currentMonth: month })
},
setCurrentMonth: month => set({ currentMonth: month }),
+ /* ---------------- actions ---------------- */
addMoneyRecord: (data) => {
- set((prev) => {
- if (!isInMonth(data.record_time, prev.currentMonth))
- return prev
-
- const category = useMoneyCategoryStore.getState().getById(data.category_id)
- const records = [...prev.records]
- const groupIndex = records.findIndex(g => g.record_time === data.record_time)
- const amount = data.amount
-
- if (groupIndex !== -1) {
- const group = { ...records[groupIndex], records: [data, ...records[groupIndex].records] }
- if (category?.type === 0)
- group.expense = add(group.expense, amount)
- if (category?.type === 1)
- group.income = add(group.income, amount)
- records[groupIndex] = group
- }
- else {
- const newGroup: MoneyRecordGroup = {
- record_time: data.record_time,
- records: [data],
- expense: category?.type === 0 ? amount : '0.00',
- income: category?.type === 1 ? amount : '0.00',
- }
- const insertIndex = records.findIndex(g => g.record_time < data.record_time)
- if (insertIndex === -1)
- records.push(newGroup)
- else records.splice(insertIndex, 0, newGroup)
- }
-
- const totalExpense = category?.type === 0 ? add(prev.total_expense, amount) : prev.total_expense
- const totalIncome = category?.type === 1 ? add(prev.total_income, amount) : prev.total_income
- const totalBalance = sub(totalIncome, totalExpense)
- return {
- ...prev,
- records,
- total_expense: totalExpense,
- total_income: totalIncome,
- total_balance: totalBalance,
- }
- })
+ if (!isInMonth(data.record_time, get().currentMonth))
+ return
+ const flatRecords = [...get().records.flatMap(g => g.records), data]
+ const newGroups = recomputeGroups(flatRecords)
+ set({ records: newGroups })
},
updateMoneyRecord: (updated) => {
- set((prev) => {
- const records = [...prev.records]
- const oldGroupIndex = records.findIndex(g => g.records.some(r => r.id === updated.id))
- if (oldGroupIndex === -1)
- return prev
-
- const updateRecordCategory = useMoneyCategoryStore.getState().getById((updated.category_id))
-
- const oldGroup = { ...records[oldGroupIndex], records: [...records[oldGroupIndex].records] }
- const oldRecordIndex = oldGroup.records.findIndex(r => r.id === updated.id)
- const oldRecord = oldGroup.records[oldRecordIndex]
- const oldRecordCategory = useMoneyCategoryStore.getState().getById((oldRecord.category_id))
-
- const oldAmount = oldRecord.amount
- const newAmount = updated.amount
-
- // 移除旧记录
- oldGroup.records.splice(oldRecordIndex, 1)
- let total_expense = prev.total_expense
- let total_income = prev.total_income
- if (oldRecordCategory?.type === 0)
- total_expense = sub(total_expense, oldAmount)
- if (oldRecordCategory?.type === 1)
- total_income = sub(total_income, oldAmount)
- if (oldGroup.records.length === 0)
- records.splice(oldGroupIndex, 1)
- else records[oldGroupIndex] = oldGroup
-
- if (!isInMonth(updated.record_time, prev.currentMonth)) {
- return { ...prev, records, total_expense, total_income }
- }
-
- // 添加到新分组
- const newGroupIndex = records.findIndex(g => g.record_time === updated.record_time)
- if (newGroupIndex !== -1) {
- const group = { ...records[newGroupIndex], records: [updated, ...records[newGroupIndex].records] }
- if (updateRecordCategory?.type === 0)
- group.expense = add(group.expense, newAmount)
- if (updateRecordCategory?.type === 1)
- group.income = add(group.income, newAmount)
- records[newGroupIndex] = group
- }
- else {
- const newGroup: MoneyRecordGroup = {
- record_time: updated.record_time,
- records: [updated],
- expense: updateRecordCategory?.type === 0 ? newAmount : '0.00',
- income: updateRecordCategory?.type === 1 ? newAmount : '0.00',
- }
- const insertIndex = records.findIndex(g => g.record_time < updated.record_time)
- if (insertIndex === -1)
- records.push(newGroup)
- else records.splice(insertIndex, 0, newGroup)
- }
-
- if (updateRecordCategory?.type === 0)
- total_expense = add(total_expense, newAmount)
- if (updateRecordCategory?.type === 1)
- total_income = add(total_income, newAmount)
-
- const total_balance = sub(total_income, total_expense)
-
- return { ...prev, records, total_expense, total_income, total_balance }
- })
+ const flatRecords = get().records.flatMap(g =>
+ g.records.map(r => r.id === updated.id ? updated : r),
+ )
+ // 如果更新后的记录不在当前月,则直接删除
+ const filteredRecords = flatRecords.filter(r => isInMonth(r.record_time, get().currentMonth))
+ const newGroups = recomputeGroups(filteredRecords)
+ set({ records: newGroups })
},
deleteMoneyRecord: (id) => {
- set((prev) => {
- const records = prev.records.map(g => ({ ...g, records: [...g.records] }))
- let total_expense = prev.total_expense
- let total_income = prev.total_income
-
- records.forEach((group) => {
- const index = group.records.findIndex(r => r.id === id)
- if (index !== -1) {
- const record = group.records[index]
- const category = useMoneyCategoryStore.getState().getById(record.category_id)
-
- if (category?.id === 0)
- total_expense = sub(total_expense, record.amount)
- if (category?.id === 1)
- total_income = sub(total_income, record.amount)
- group.records.splice(index, 1)
- }
- })
-
- const newRecords = records.filter(g => g.records.length > 0)
-
- const total_balance = sub(total_income, total_expense)
-
- return { ...prev, records: newRecords, total_expense, total_income, total_balance }
- })
+ const flatRecords = get().records.flatMap(g => g.records).filter(r => r.id !== id)
+ const newGroups = recomputeGroups(flatRecords)
+ set({ records: newGroups })
deleteMoneyRecordApi({ id }).catch(console.error)
},
- getMoneyRecordById: (id: number) => {
- const records = get().records
- for (const group of records) {
+ getMoneyRecordById: (id) => {
+ for (const group of get().records) {
const record = group.records.find(r => r.id === id)
if (record)
return record
diff --git a/code/allapp/allapp-rn/src/types/money/category.ts b/code/allapp/allapp-rn/src/types/money/category.ts
index dfb80b65..3944158d 100644
--- a/code/allapp/allapp-rn/src/types/money/category.ts
+++ b/code/allapp/allapp-rn/src/types/money/category.ts
@@ -38,6 +38,7 @@ export interface CreateMoneyCategoryParams {
name: string
icon: string
type: MoneyCategoryEnum
+ sort_number: number
}
export interface CreateMoneyCategoryResult {