u
This commit is contained in:
1 parent
d4082a54d5
commit
cb4562361a
8 files changed
+281
-144
No files matched your search
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Pressable, ScrollView, Text, View } from 'react-native'
|
||||
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerBackdrop,
|
||||
DrawerBody,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
} from '@/components/ui/drawer'
|
||||
import { Input, InputField } from '@/components/ui/input'
|
||||
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
|
||||
interface CreateCategoryProps {
|
||||
open: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export default function CreateCategory({
|
||||
open,
|
||||
onClose,
|
||||
}: CreateCategoryProps) {
|
||||
const moneyCategoryStore = useMoneyCategoryStore()
|
||||
|
||||
const [isOpen, setIsOpen] = useState(open)
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(open)
|
||||
}, [open])
|
||||
|
||||
function handleClose() {
|
||||
setIsOpen(false)
|
||||
onClose && onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer isOpen={isOpen} onClose={handleClose} size="lg" anchor="bottom">
|
||||
<DrawerBackdrop />
|
||||
<DrawerContent
|
||||
style={{
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
>
|
||||
<DrawerHeader className="px-4 flex flex-col gap-4">
|
||||
<View className="flex flex-row items-center justify-between w-full">
|
||||
<Pressable>
|
||||
<Text className="text-gray-600">取消</Text>
|
||||
</Pressable>
|
||||
|
||||
<Text className="text-xl font-medium">类别</Text>
|
||||
|
||||
<Pressable>
|
||||
<Text className="text-gray-600">确认</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Input
|
||||
variant="outline"
|
||||
>
|
||||
<InputField placeholder="请输入名称" />
|
||||
</Input>
|
||||
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<ScrollView className="flex-1">
|
||||
{Object.entries(moneyCategoryStore.ICON_GROUPS).map(([groupName, icons]) => (
|
||||
<View key={groupName} className="mb-4">
|
||||
<Text className="text-center mb-4">
|
||||
{groupName}
|
||||
</Text>
|
||||
|
||||
<View className="flex flex-row flex-wrap">
|
||||
{icons.map(icon => (
|
||||
<Pressable
|
||||
key={icon}
|
||||
className="w-1/5 mb-3 flex items-center"
|
||||
onPress={() => {
|
||||
console.log('select icon:', icon)
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<MoneyCategoryIcon name={icon} size={28} />
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ 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'
|
||||
import CreateCategory from './components/CreateCategory'
|
||||
|
||||
export default function MoneyCategorySetting() {
|
||||
const [selectedTab, setSelectedTab] = useState(0)
|
||||
@@ -21,6 +22,8 @@ export default function MoneyCategorySetting() {
|
||||
const [draggingDeleteZone, setDraggingDeleteZone] = useState<boolean>(false)
|
||||
const [deleteTimer, setDeleteTimer] = useState<number | null>(null)
|
||||
|
||||
const [openCreateCategory, setOpenCreateCategory] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
setCategories(selectedTab === 0 ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
|
||||
}, [selectedTab])
|
||||
@@ -90,6 +93,9 @@ export default function MoneyCategorySetting() {
|
||||
color: '#fff',
|
||||
clickable: false,
|
||||
}}
|
||||
onPress={(() =>
|
||||
setOpenCreateCategory(true)
|
||||
)}
|
||||
>
|
||||
新增
|
||||
</AButton>
|
||||
@@ -114,14 +120,16 @@ export default function MoneyCategorySetting() {
|
||||
Vibration.vibrate(50)
|
||||
setDraggingId(data.key)
|
||||
}}
|
||||
onDragEnd={(data) => {
|
||||
onDragEnd={(dragData) => {
|
||||
setDraggingId('')
|
||||
if (draggingDeleteZone)
|
||||
return
|
||||
|
||||
const newOrder = data.data
|
||||
const newData = dragData.data
|
||||
|
||||
moneyCategoryStore.updateCategories(newOrder, selectedTab)
|
||||
if (JSON.stringify(categories) !== JSON.stringify(newData)) {
|
||||
moneyCategoryStore.updateSortNumbers(newData, selectedTab)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
@@ -139,7 +147,7 @@ export default function MoneyCategorySetting() {
|
||||
const deleteId = Number(draggingId)
|
||||
setCategories(prev => prev.filter(c => c.id !== deleteId))
|
||||
|
||||
moneyCategoryStore.deleteCategoryById(deleteId, selectedTab)
|
||||
moneyCategoryStore.deleteCategoryById(deleteId)
|
||||
|
||||
setDraggingId('')
|
||||
setDraggingDeleteZone(false)
|
||||
@@ -179,6 +187,11 @@ export default function MoneyCategorySetting() {
|
||||
</Sortable.MultiZoneProvider>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<CreateCategory
|
||||
open={openCreateCategory}
|
||||
onClose={() => setOpenCreateCategory(false)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export default function PagerWithTab() {
|
||||
|
||||
const fetchData = async () => {
|
||||
await Promise.all([
|
||||
getUserSpaceApi({})
|
||||
getUserSpaceApi()
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
if (res.data === null) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type {
|
||||
ExitSpaceParams,
|
||||
ExitSpaceResult,
|
||||
GetUserSpaceParams,
|
||||
GetUserSpaceResult,
|
||||
JoinSpaceParams,
|
||||
JoinSpaceResult,
|
||||
ListSpaceUsersParams,
|
||||
@@ -14,8 +12,8 @@ import alovaInstance from '../../index'
|
||||
|
||||
const API_PREFIX = '/space/user'
|
||||
|
||||
export function getUserSpaceApi(params: GetUserSpaceParams) {
|
||||
return alovaInstance.Get<ResponseData<GetUserSpaceResult | null>>(`${API_PREFIX}/get`, params)
|
||||
export function getUserSpaceApi() {
|
||||
return alovaInstance.Get<ResponseData<UserSpace | null>>(`${API_PREFIX}/get`)
|
||||
}
|
||||
|
||||
export function exitSpaceApi(params: ExitSpaceParams) {
|
||||
|
||||
@@ -18,7 +18,6 @@ export const useAppStore = create<AppState>((set, get) => ({
|
||||
setReady: (v: boolean) => set({ ready: v }),
|
||||
|
||||
init: async () => {
|
||||
// 如果已经初始化过就不重复执行(除非你手动 setReady(false) 再 init)
|
||||
if (get().ready)
|
||||
return
|
||||
|
||||
@@ -40,7 +39,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
||||
await moneyCategoryStore.init()
|
||||
|
||||
// 获取空间信息
|
||||
const res = await getUserSpaceApi({})
|
||||
const res = await getUserSpaceApi()
|
||||
spaceStore.setUserSpace(res.data)
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -1,147 +1,180 @@
|
||||
import type { MoneyCategoryItem, UpdateMoneyCategorySortItem } from '@/types/money/category'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||
import { deleteMoneyCategoryApi, queryMoneyCategoryApi, updateMoneyCategorySortApi } from '@/request/api/money/category'
|
||||
|
||||
interface MoneyCategoryStore {
|
||||
ICON_GROUPS: Record<string, string[]>
|
||||
categories: MoneyCategoryItem[]
|
||||
|
||||
init: () => Promise<void>
|
||||
setCategories: (categories: MoneyCategoryItem[]) => void
|
||||
addCategory: (category: MoneyCategoryItem) => void
|
||||
removeCategory: (id: number) => void
|
||||
init: () => Promise<void>
|
||||
|
||||
getExpenseCategories: () => MoneyCategoryItem[]
|
||||
getIncomeCategories: () => MoneyCategoryItem[]
|
||||
|
||||
getCategoryById: (id: number) => MoneyCategoryItem | null
|
||||
|
||||
updateCategories: (data: MoneyCategoryItem[], type: number) => void
|
||||
deleteCategoryById: (id: number, type: number) => void
|
||||
|
||||
sysCategories: MoneyCategoryItem[]
|
||||
setSysCategories: (categories: MoneyCategoryItem[]) => void
|
||||
updateSortNumbers: (data: MoneyCategoryItem[], type: number) => void
|
||||
deleteCategoryById: (id: number) => void
|
||||
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
export const useMoneyCategoryStore = create<MoneyCategoryStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
categories: [],
|
||||
sysCategories: [],
|
||||
(set, get) => ({
|
||||
ICON_GROUPS: {
|
||||
餐饮: [
|
||||
'money_canyin',
|
||||
'money_yinpin',
|
||||
'money_lingshi',
|
||||
'money_xiaoye',
|
||||
],
|
||||
娱乐: [
|
||||
'money_yule',
|
||||
'money_youxi',
|
||||
'money_dianying',
|
||||
],
|
||||
交通: [
|
||||
'money_jiaotong',
|
||||
'money_tingchefei',
|
||||
'money_jiayou',
|
||||
],
|
||||
生活消费: [
|
||||
'money_lijin',
|
||||
'money_jiadian',
|
||||
'money_riyongpin',
|
||||
'money_shucai',
|
||||
'money_meifa',
|
||||
'money_shuidian',
|
||||
'money_wenju',
|
||||
'money_zhufang',
|
||||
'money_baoxian',
|
||||
'money_baojianpin',
|
||||
'money_huafei',
|
||||
'money_lvyou',
|
||||
'money_gouwu',
|
||||
'money_shuben',
|
||||
'money_chongwu',
|
||||
'money_ruanjian',
|
||||
],
|
||||
健康: [
|
||||
'money_tijian',
|
||||
'money_yao',
|
||||
'money_jiuyi',
|
||||
],
|
||||
公益: [
|
||||
'money_gongyi',
|
||||
],
|
||||
兼职与收入: [
|
||||
'money_gongzi',
|
||||
'money_jianzhi',
|
||||
],
|
||||
鲜花与水果: [
|
||||
'money_shuiguo',
|
||||
'money_xianhua',
|
||||
],
|
||||
转账与转移: [
|
||||
'money_zhuanzhang',
|
||||
],
|
||||
家庭与家电: [
|
||||
'money_kuaidi',
|
||||
'money_weixiu',
|
||||
'money_jiaju',
|
||||
'money_muying',
|
||||
'money_liwu',
|
||||
],
|
||||
运动与娱乐: [
|
||||
'money_yundong',
|
||||
'money_peixun',
|
||||
],
|
||||
},
|
||||
|
||||
setCategories: categories => set({ categories }),
|
||||
setSysCategories: categories => set({ categories }),
|
||||
categories: [],
|
||||
|
||||
addCategory: category =>
|
||||
set({ categories: [...get().categories, category] }),
|
||||
setCategories: categories => set({ categories }),
|
||||
|
||||
removeCategory: id =>
|
||||
set({ categories: get().categories.filter(c => c.id !== id) }),
|
||||
addCategory: category =>
|
||||
set({ categories: [...get().categories, category] }),
|
||||
|
||||
init: async () => {
|
||||
try {
|
||||
await queryMoneyCategoryApi().then((res) => {
|
||||
if (res.isSuccess) {
|
||||
set({ categories: res.data.categories || [] })
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
}).finally(() => {
|
||||
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Fetch category failed:', e)
|
||||
}
|
||||
},
|
||||
|
||||
clear: () => {
|
||||
set({ categories: [] })
|
||||
},
|
||||
|
||||
getExpenseCategories: () => (get().categories || []).filter(c => c.type === 0),
|
||||
getIncomeCategories: () => (get().categories || []).filter(c => c.type === 1),
|
||||
|
||||
getCategoryById: (id: number) => {
|
||||
return get().categories.find(c => c.id === id) || null
|
||||
},
|
||||
|
||||
getSysCategoryById: (id: number) => {
|
||||
return get().sysCategories.find(c => c.id === id) || null
|
||||
},
|
||||
|
||||
updateCategories: (data, type) => {
|
||||
// 获取当前所有分类
|
||||
const allCategories = get().categories
|
||||
|
||||
// 保留另一类分类
|
||||
const otherCategories = allCategories.filter(c => c.type !== type)
|
||||
|
||||
// 给当前类型的数据重新计算 sort_number
|
||||
const updatedData = data.map((item, index) => ({
|
||||
...item,
|
||||
sort_number: index, // 按顺序重新赋值
|
||||
}))
|
||||
|
||||
// 合并另一类分类
|
||||
const finalCategories = [...otherCategories, ...updatedData]
|
||||
|
||||
// 构造后端请求 payload,只需要 id 和 sort_number
|
||||
const payload: UpdateMoneyCategorySortItem[] = updatedData.map(item => ({
|
||||
id: item.id,
|
||||
sort_number: item.sort_number,
|
||||
}))
|
||||
|
||||
// 调用后端接口更新排序
|
||||
updateMoneyCategorySortApi({
|
||||
data: payload,
|
||||
}).then((res) => {
|
||||
console.log('后端返回:', res)
|
||||
init: async () => {
|
||||
try {
|
||||
await queryMoneyCategoryApi().then((res) => {
|
||||
if (res.isSuccess) {
|
||||
set({ categories: res.data.categories || [] })
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
}).finally(() => {
|
||||
|
||||
})
|
||||
|
||||
// 更新前端 state
|
||||
set({ categories: finalCategories })
|
||||
},
|
||||
|
||||
deleteCategoryById: async (id: number, type: number) => {
|
||||
const allCategories = get().categories
|
||||
|
||||
// 删除指定 id,同时只处理同类型的数据
|
||||
const finalCategories = allCategories
|
||||
.filter(c => c.id !== id)
|
||||
.map((item, index) => {
|
||||
if (item.type === type) {
|
||||
return { ...item, sort_number: index }
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
// 更新 state
|
||||
set({ categories: finalCategories })
|
||||
|
||||
// 调用删除接口
|
||||
await deleteMoneyCategoryApi({ id }).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
// 同步更新排序给后端,仅同类型
|
||||
const payload = finalCategories
|
||||
.filter(c => c.type === type)
|
||||
.map(item => ({
|
||||
id: item.id,
|
||||
sort_number: item.sort_number,
|
||||
}))
|
||||
await updateMoneyCategorySortApi({ data: payload })
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'money-category-storage',
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Fetch category failed:', e)
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
clear: () => {
|
||||
set({ categories: [] })
|
||||
},
|
||||
|
||||
getExpenseCategories: () => (get().categories || []).filter(c => c.type === 0),
|
||||
getIncomeCategories: () => (get().categories || []).filter(c => c.type === 1),
|
||||
|
||||
getCategoryById: (id: number) => {
|
||||
return get().categories.find(c => c.id === id) || null
|
||||
},
|
||||
|
||||
updateSortNumbers: (data, type) => {
|
||||
// 获取当前所有分类
|
||||
const allCategories = get().categories
|
||||
|
||||
// 保留另一类分类
|
||||
const otherCategories = allCategories.filter(c => c.type !== type)
|
||||
|
||||
// 给当前类型的数据重新计算 sort_number
|
||||
const updatedData = data.map((item, index) => ({
|
||||
...item,
|
||||
sort_number: index, // 按顺序重新赋值
|
||||
}))
|
||||
|
||||
// 合并另一类分类
|
||||
const finalCategories = [...otherCategories, ...updatedData]
|
||||
|
||||
// 构造后端请求 payload,只需要 id 和 sort_number
|
||||
const payload: UpdateMoneyCategorySortItem[] = updatedData.map(item => ({
|
||||
id: item.id,
|
||||
sort_number: item.sort_number,
|
||||
}))
|
||||
|
||||
// 调用后端接口更新排序
|
||||
updateMoneyCategorySortApi({
|
||||
data: payload,
|
||||
}).then((res) => {
|
||||
console.log('后端返回:', res)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
// 更新前端 state
|
||||
set({ categories: finalCategories })
|
||||
},
|
||||
|
||||
deleteCategoryById: async (id: number) => {
|
||||
const allCategories = get().categories
|
||||
|
||||
// 删除指定 id,同时只处理同类型的数据
|
||||
const finalCategories = allCategories
|
||||
.filter(c => c.id !== id)
|
||||
|
||||
// 更新 state
|
||||
set({ categories: finalCategories })
|
||||
|
||||
// 调用删除接口
|
||||
await deleteMoneyCategoryApi({ id }).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
},
|
||||
}),
|
||||
|
||||
)
|
||||
@@ -42,7 +42,7 @@ export const useMoneyRecordStore = create<MoneyRecordStore>((set, get) => ({
|
||||
if (!isInMonth(data.record_time, prev.currentMonth))
|
||||
return prev
|
||||
|
||||
const category = useMoneyCategoryStore.getState().getCategoryById(data.category_id)
|
||||
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
|
||||
@@ -88,12 +88,12 @@ export const useMoneyRecordStore = create<MoneyRecordStore>((set, get) => ({
|
||||
if (oldGroupIndex === -1)
|
||||
return prev
|
||||
|
||||
const updateRecordCategory = useMoneyCategoryStore.getState().getCategoryById((updated.category_id))
|
||||
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().getCategoryById((oldRecord.category_id))
|
||||
const oldRecordCategory = useMoneyCategoryStore.getState().getById((oldRecord.category_id))
|
||||
|
||||
const oldAmount = oldRecord.amount
|
||||
const newAmount = updated.amount
|
||||
@@ -158,7 +158,7 @@ export const useMoneyRecordStore = create<MoneyRecordStore>((set, get) => ({
|
||||
const index = group.records.findIndex(r => r.id === id)
|
||||
if (index !== -1) {
|
||||
const record = group.records[index]
|
||||
const category = useMoneyCategoryStore.getState().getCategoryById(record.category_id)
|
||||
const category = useMoneyCategoryStore.getState().getById(record.category_id)
|
||||
|
||||
if (category?.id === 0)
|
||||
total_expense = sub(total_expense, record.amount)
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
// 获取用户的空间
|
||||
export interface GetUserSpaceParams {
|
||||
|
||||
}
|
||||
export interface GetUserSpaceResult {
|
||||
space_id: number
|
||||
space_name: string
|
||||
role: number
|
||||
invite_code: string
|
||||
}
|
||||
|
||||
// 退出空间
|
||||
export interface ExitSpaceParams {
|
||||
|
||||
|
||||
Reference in new issue
Block a user