This commit is contained in:
oneao committed 2026-01-16 17:27:34 +08:00
1 parent 44ab5416a3
commit 1935906676
17 files changed
+322 -266

No files matched your search

@@ -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<string, string[]>
@@ -8,15 +9,17 @@ interface MoneyCategoryStore {
init: () => Promise<void>
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<void>
updateSortNumbers: (data: MoneyCategoryItem[], type: number) => Promise<void>
deleteCategoryById: (id: number) => Promise<void>
isDuplicateCategory: (name: string, type: number) => boolean
clear: () => void
}
@@ -94,9 +97,6 @@ export const useMoneyCategoryStore = create<MoneyCategoryStore>()(
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<MoneyCategoryStore>()(
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)
},
}),
)
+57 -143
View File
@@ -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<string, MoneyRecordGroup> = {}
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<MoneyRecordStore>((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