u
This commit is contained in:
1 parent
d9c49b34db
commit
d33cef825a
36 files changed
+753
-1077
No files matched your search
@@ -29,6 +29,7 @@
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-constants": "~18.0.13",
|
||||
"expo-font": "~14.0.11",
|
||||
"expo-keep-awake": "~15.0.8",
|
||||
"expo-linear-gradient": "~15.0.8",
|
||||
"expo-linking": "~8.0.11",
|
||||
"expo-qq": "^0.1.6",
|
||||
|
||||
Generated
+3
@@ -65,6 +65,9 @@ importers:
|
||||
expo-font:
|
||||
specifier: ~14.0.11
|
||||
version: 14.0.11(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
expo-keep-awake:
|
||||
specifier: ~15.0.8
|
||||
version: 15.0.8(expo@54.0.32)(react@19.1.0)
|
||||
expo-linear-gradient:
|
||||
specifier: ~15.0.8
|
||||
version: 15.0.8(expo@54.0.32)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
@@ -69,16 +69,6 @@ export default function StaticTransactions({
|
||||
data: group.records,
|
||||
}))
|
||||
|
||||
if (records.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
type="empty"
|
||||
title="暂无记账记录"
|
||||
description="快去添加你的第一笔记账吧 "
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
sections={sections}
|
||||
@@ -90,6 +80,15 @@ export default function StaticTransactions({
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||
}
|
||||
ListEmptyComponent={() => (
|
||||
<View className="flex-1 justify-center py-20">
|
||||
<EmptyState
|
||||
type="empty"
|
||||
title="暂无记账记录"
|
||||
description="快去添加你的第一笔记账吧"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
renderSectionHeader={({ section }) => (
|
||||
<View className="bg-white py-2 px-4 border-b border-gray-50">
|
||||
<View className="flex-row items-center justify-between">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { ADatePicker, ADrawer, AListItem, LucideIcon } from '@/components/common/ao'
|
||||
import { Fab, FabIcon } from '@/components/ui/fab'
|
||||
import { AddIcon } from '@/components/ui/icon'
|
||||
import { queryMoneyRecordsApi } from '@/request/api/money/record'
|
||||
import { deleteMoneyRecordApi, listMoneyRecordsApi } from '@/request/api/money/record'
|
||||
import { useMoneyRecordStore } from '@/store/moneyRecord'
|
||||
import { getCurrentYearMonth } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
@@ -26,12 +26,11 @@ export default function Money() {
|
||||
// 操作菜单
|
||||
const [showActionsMenu, setShowActionsMenu] = useState(false)
|
||||
|
||||
function getMoneyRecords() {
|
||||
queryMoneyRecordsApi({
|
||||
function fetchMoneyRecords() {
|
||||
listMoneyRecordsApi({
|
||||
record_time: searchParams.record_time,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
console.log(res.data.records)
|
||||
moneyRecordStore.setRecords(res.data.records || [], searchParams.record_time)
|
||||
}
|
||||
else {
|
||||
@@ -43,18 +42,20 @@ export default function Money() {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
// 页面显示 / 返回时
|
||||
console.log('===show===')
|
||||
|
||||
return () => {
|
||||
// 页面离开(可选)
|
||||
}
|
||||
}, []),
|
||||
fetchMoneyRecords()
|
||||
}, [searchParams.record_time]),
|
||||
)
|
||||
|
||||
async function handleDeleteRecord(id: number) {
|
||||
await deleteMoneyRecordApi({
|
||||
id,
|
||||
})
|
||||
|
||||
fetchMoneyRecords()
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
className="flex-1"
|
||||
@@ -129,22 +130,6 @@ export default function Money() {
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* <View
|
||||
className="flex-row items-center gap-3 mt-2"
|
||||
style={{
|
||||
borderBottomLeftRadius: 8,
|
||||
borderBottomRightRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text className="text-sm text-gray-600 text-right">预算</Text>
|
||||
<View className="flex-1">
|
||||
<Progress size="sm">
|
||||
<ProgressFilledTrack />
|
||||
</Progress>
|
||||
</View>
|
||||
<Text className="text-xs text-blue-500">点击设置</Text>
|
||||
</View> */}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -164,10 +149,10 @@ export default function Money() {
|
||||
})
|
||||
}}
|
||||
onDelete={(id) => {
|
||||
moneyRecordStore.deleteMoneyRecord(id)
|
||||
handleDeleteRecord(id)
|
||||
}}
|
||||
onRefreshData={() => {
|
||||
getMoneyRecords()
|
||||
fetchMoneyRecords()
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEvent } from 'expo'
|
||||
// import ExpoQQ from 'expo-qq'
|
||||
import ExpoQQ from 'expo-qq'
|
||||
import { useRouter } from 'expo-router'
|
||||
// import ExpoWeChat from 'expo-wechat'
|
||||
import ExpoWeChat from 'expo-wechat'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Image, Text, View } from 'react-native'
|
||||
import { AButton, ACheckbox } from '@/components/common/ao'
|
||||
@@ -23,167 +23,167 @@ export default function Auth() {
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished')
|
||||
// const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo')
|
||||
// const authResult = useEvent(ExpoWeChat, 'onAuthResult')
|
||||
const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished')
|
||||
const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo')
|
||||
const authResult = useEvent(ExpoWeChat, 'onAuthResult')
|
||||
|
||||
// async function onQQLogin() {
|
||||
// setLoading(true)
|
||||
// setForm(val => ({
|
||||
// ...val,
|
||||
// type: 1,
|
||||
// }))
|
||||
// await ExpoQQ.login(['get_user_info'])
|
||||
// }
|
||||
async function onQQLogin() {
|
||||
setLoading(true)
|
||||
setForm(val => ({
|
||||
...val,
|
||||
type: 1,
|
||||
}))
|
||||
await ExpoQQ.login(['get_user_info'])
|
||||
}
|
||||
|
||||
// async function onWeChatLogin() {
|
||||
// setLoading(true)
|
||||
async function onWeChatLogin() {
|
||||
setLoading(true)
|
||||
|
||||
// // 检查微信是否安装
|
||||
// const isInstalled = await ExpoWeChat.isWXAppInstalled()
|
||||
// if (!isInstalled) {
|
||||
// toast.show({
|
||||
// title: onLoginFinished?.errorMessage || '请先安装微信',
|
||||
// })
|
||||
// setLoading(false)
|
||||
// return
|
||||
// }
|
||||
// 检查微信是否安装
|
||||
const isInstalled = await ExpoWeChat.isWXAppInstalled()
|
||||
if (!isInstalled) {
|
||||
toast.show({
|
||||
title: onLoginFinished?.errorMessage || '请先安装微信',
|
||||
})
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// setForm(val => ({
|
||||
// ...val,
|
||||
// type: 0,
|
||||
// }))
|
||||
setForm(val => ({
|
||||
...val,
|
||||
type: 0,
|
||||
}))
|
||||
|
||||
// ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123')
|
||||
// }
|
||||
ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123')
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// ExpoQQ.init('102826474', '')
|
||||
// ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '')
|
||||
// }, [])
|
||||
useEffect(() => {
|
||||
ExpoQQ.init('102826474', '')
|
||||
ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '')
|
||||
}, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!onLoginFinished) {
|
||||
// return
|
||||
// }
|
||||
useEffect(() => {
|
||||
if (!onLoginFinished) {
|
||||
return
|
||||
}
|
||||
|
||||
// if (!onLoginFinished.success) {
|
||||
// toast.show({
|
||||
// title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
|
||||
// })
|
||||
// setLoading(false)
|
||||
// return
|
||||
// }
|
||||
if (!onLoginFinished.success) {
|
||||
toast.show({
|
||||
title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
|
||||
})
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// const fetchUserInfo = async () => {
|
||||
// try {
|
||||
// const tokenInfo = await ExpoQQ.getLoginTokenInfo()
|
||||
// if (!tokenInfo.openId) {
|
||||
// toast.show({
|
||||
// title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
|
||||
// })
|
||||
// setLoading(false)
|
||||
// return
|
||||
// }
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const tokenInfo = await ExpoQQ.getLoginTokenInfo()
|
||||
if (!tokenInfo.openId) {
|
||||
toast.show({
|
||||
title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权',
|
||||
})
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// setForm(val => ({
|
||||
// ...val,
|
||||
// openid: String(tokenInfo.openId),
|
||||
// }))
|
||||
setForm(val => ({
|
||||
...val,
|
||||
openid: String(tokenInfo.openId),
|
||||
}))
|
||||
|
||||
// await ExpoQQ.sendGetUserInfoReq()
|
||||
// }
|
||||
// catch (e) {
|
||||
// console.error('QQ 登录未完成', e)
|
||||
// setLoading(false)
|
||||
// }
|
||||
// }
|
||||
await ExpoQQ.sendGetUserInfoReq()
|
||||
}
|
||||
catch (e) {
|
||||
console.error('QQ 登录未完成', e)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// fetchUserInfo()
|
||||
// }, [onLoginFinished])
|
||||
fetchUserInfo()
|
||||
}, [onLoginFinished])
|
||||
|
||||
// // QQ登录
|
||||
// useEffect(() => {
|
||||
// const handleLogin = async () => {
|
||||
// if (!onGetUserInfo)
|
||||
// return
|
||||
// QQ登录
|
||||
useEffect(() => {
|
||||
const handleLogin = async () => {
|
||||
if (!onGetUserInfo)
|
||||
return
|
||||
|
||||
// try {
|
||||
// const loginRes = await loginQqApi({
|
||||
// openid: form.openid,
|
||||
// username: onGetUserInfo.nickname || '',
|
||||
// avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2,
|
||||
// })
|
||||
try {
|
||||
const loginRes = await loginQqApi({
|
||||
openid: form.openid,
|
||||
username: onGetUserInfo.nickname || '',
|
||||
avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2,
|
||||
})
|
||||
|
||||
// if (loginRes.isSuccess) {
|
||||
// userStore.setUserInfo({
|
||||
// token: loginRes.data.token,
|
||||
// id: loginRes.data.userId,
|
||||
// username: loginRes.data.username,
|
||||
// avatar: loginRes.data.avatar,
|
||||
// })
|
||||
if (loginRes.isSuccess) {
|
||||
userStore.setUserInfo({
|
||||
token: loginRes.data.token,
|
||||
id: loginRes.data.userId,
|
||||
username: loginRes.data.username,
|
||||
avatar: loginRes.data.avatar,
|
||||
})
|
||||
|
||||
// console.log(loginRes.data.token)
|
||||
console.log(loginRes.data.token)
|
||||
|
||||
// router.push('/')
|
||||
// }
|
||||
// }
|
||||
// catch (err) {
|
||||
// console.error('登录接口出错', err)
|
||||
// }
|
||||
// finally {
|
||||
// setLoading(false)
|
||||
// }
|
||||
// }
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('登录接口出错', err)
|
||||
}
|
||||
finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// handleLogin()
|
||||
// }, [onGetUserInfo])
|
||||
handleLogin()
|
||||
}, [onGetUserInfo])
|
||||
|
||||
// // 微信登录
|
||||
// useEffect(() => {
|
||||
// if (!authResult)
|
||||
// return
|
||||
// 微信登录
|
||||
useEffect(() => {
|
||||
if (!authResult)
|
||||
return
|
||||
|
||||
// const handleLogin = async () => {
|
||||
// if (authResult.errorCode === 0) {
|
||||
// try {
|
||||
// const loginRes = await loginWxApi({
|
||||
// code: authResult.code,
|
||||
// })
|
||||
const handleLogin = async () => {
|
||||
if (authResult.errorCode === 0) {
|
||||
try {
|
||||
const loginRes = await loginWxApi({
|
||||
code: authResult.code,
|
||||
})
|
||||
|
||||
// if (loginRes.isSuccess) {
|
||||
// userStore.setUserInfo({
|
||||
// token: loginRes.data.token,
|
||||
// id: loginRes.data.userId,
|
||||
// username: loginRes.data.username,
|
||||
// avatar: loginRes.data.avatar,
|
||||
// })
|
||||
if (loginRes.isSuccess) {
|
||||
userStore.setUserInfo({
|
||||
token: loginRes.data.token,
|
||||
id: loginRes.data.userId,
|
||||
username: loginRes.data.username,
|
||||
avatar: loginRes.data.avatar,
|
||||
})
|
||||
|
||||
// router.push('/')
|
||||
// }
|
||||
// }
|
||||
// catch (err) {
|
||||
// console.error('登录接口出错', err)
|
||||
// }
|
||||
// finally {
|
||||
// setLoading(false)
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// console.log('授权失败:', authResult.errorMessage)
|
||||
// setLoading(false)
|
||||
// }
|
||||
// }
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('登录接口出错', err)
|
||||
}
|
||||
finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('授权失败:', authResult.errorMessage)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// handleLogin()
|
||||
// }, [authResult])
|
||||
handleLogin()
|
||||
}, [authResult])
|
||||
|
||||
function testLogin() {
|
||||
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs
|
||||
|
||||
userStore.setUserInfo({
|
||||
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs',
|
||||
id: 766785982165061,
|
||||
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3Njc4NjM3NTU5MzE3MTcsImlhdCI6MTc2OTYwMzEzOX0.xfW-uyFBi799u4VJ-93A88c2CC8jwpYOPsALUuaIizg',
|
||||
id: 767863755931717,
|
||||
username: 'Zhang',
|
||||
avatar: 'http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100',
|
||||
})
|
||||
@@ -208,7 +208,7 @@ export default function Auth() {
|
||||
</View>
|
||||
|
||||
<View className="flex flex-col gap-4">
|
||||
{/* <AButton
|
||||
<AButton
|
||||
leftIcon={
|
||||
<IconWeixin color="#fff" />
|
||||
}
|
||||
@@ -227,7 +227,7 @@ export default function Auth() {
|
||||
onPress={onQQLogin}
|
||||
>
|
||||
QQ
|
||||
</AButton> */}
|
||||
</AButton>
|
||||
|
||||
<AButton
|
||||
variant="outline"
|
||||
|
||||
@@ -33,10 +33,10 @@ export default function CreateCategory({
|
||||
const [form, setForm] = useState<EditCategoryForm>({ icon: '', name: '' })
|
||||
const [confirmDisabled, setConfirmDisabled] = useState(true)
|
||||
const [confirmLoading, setConfirmLoading] = useState(false)
|
||||
const flatListData = Object.entries(moneyCategoryStore.ICON_GROUPS)
|
||||
const flatListData2 = Object.values(moneyCategoryStore.ICON_GROUPS).flat()
|
||||
|
||||
const scrollRef = useRef<any>(null)
|
||||
const iconPositions = useRef<Record<string, number>>({}) // 记录每个 icon 的 y 坐标
|
||||
|
||||
const flatListRef = useRef<FlatList<any>>(null)
|
||||
// Drawer 打开/关闭控制
|
||||
useEffect(() => {
|
||||
setIsOpen(open)
|
||||
@@ -47,6 +47,22 @@ export default function CreateCategory({
|
||||
// 确认按钮状态
|
||||
useEffect(() => {
|
||||
setConfirmDisabled(!(form.name && form.name.length <= 5 && form.icon))
|
||||
|
||||
const itemIndex = flatListData2.findIndex(item => item === form.icon)
|
||||
if (itemIndex === -1 || !flatListRef.current)
|
||||
return
|
||||
|
||||
if (form.icon) {
|
||||
setTimeout(() => {
|
||||
if (flatListRef.current) {
|
||||
flatListRef.current.scrollToIndex({
|
||||
index: Math.floor(itemIndex / 5),
|
||||
animated: true,
|
||||
viewPosition: 0.5,
|
||||
})
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
}, [form.name, form.icon])
|
||||
|
||||
// 编辑已有分类时填充数据
|
||||
@@ -61,62 +77,55 @@ export default function CreateCategory({
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
// 缓存点击 Icon 的回调
|
||||
const handlePressIcon = useCallback(
|
||||
(icon: string) => {
|
||||
setForm(v => ({ ...v, icon }))
|
||||
},
|
||||
[setForm],
|
||||
)
|
||||
|
||||
// Memo 化单个 IconItem,避免重复渲染
|
||||
const IconItem = useCallback(({ icon, selected }: { icon: string, selected: boolean }) => {
|
||||
return (
|
||||
<Pressable
|
||||
className="w-1/5 mb-3 flex items-center"
|
||||
onPress={() => handlePressIcon(icon)}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: selected ? '#333' : '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<MoneyCategoryIcon
|
||||
name={icon}
|
||||
size={32}
|
||||
color={selected ? '#fff' : '#000'}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}, [handlePressIcon])
|
||||
|
||||
// Memo 化渲染每组 Icon 的组件
|
||||
// 渲染每组 Icon
|
||||
const renderGroup = useCallback(
|
||||
({ item }: { item: [string, string[]] }) => {
|
||||
const [groupName, icons] = item
|
||||
|
||||
return (
|
||||
<View className="mb-4">
|
||||
<Text className="text-center text-sm mb-3">{groupName}</Text>
|
||||
|
||||
<View className="flex flex-row flex-wrap">
|
||||
{icons.map(icon => (
|
||||
<IconItem key={icon} icon={icon} selected={form.icon === icon} />
|
||||
))}
|
||||
{icons.map((icon) => {
|
||||
const selected = form.icon === icon
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={icon}
|
||||
className="w-1/5 mb-3 flex items-center"
|
||||
onPress={() =>
|
||||
setForm(v => ({
|
||||
...v,
|
||||
icon,
|
||||
}))}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: selected ? '#333' : '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<MoneyCategoryIcon
|
||||
name={icon}
|
||||
size={32}
|
||||
color={selected ? '#fff' : '#000'}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
},
|
||||
[IconItem, form.icon],
|
||||
[form.icon],
|
||||
)
|
||||
|
||||
// 使用 useMemo 缓存 FlatList 的数据
|
||||
const flatListData = useMemo(() => Object.entries(moneyCategoryStore.ICON_GROUPS), [moneyCategoryStore.ICON_GROUPS])
|
||||
|
||||
return (
|
||||
<ADrawer
|
||||
visible={isOpen}
|
||||
@@ -153,10 +162,13 @@ export default function CreateCategory({
|
||||
)}
|
||||
>
|
||||
<FlatList
|
||||
ref={scrollRef}
|
||||
ref={flatListRef}
|
||||
data={flatListData}
|
||||
keyExtractor={([groupName]) => groupName}
|
||||
renderItem={renderGroup}
|
||||
getItemLayout={(data, index) => {
|
||||
return { length: 80, offset: 80 * index, index }
|
||||
}}
|
||||
/>
|
||||
</ADrawer>
|
||||
)
|
||||
|
||||
@@ -88,13 +88,6 @@ export default function MoenyRecordEditor() {
|
||||
record_time: moneyRecord.record_time,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
const result: MoneyRecord = {
|
||||
...moneyRecord,
|
||||
category_id: selectedCategory.id,
|
||||
}
|
||||
|
||||
moneyRecordStore.updateMoneyRecord(result)
|
||||
|
||||
router.back()
|
||||
}
|
||||
}).catch((err) => {
|
||||
@@ -110,13 +103,6 @@ export default function MoenyRecordEditor() {
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
const result: MoneyRecord = {
|
||||
...res.data,
|
||||
category_id: selectedCategory.id,
|
||||
}
|
||||
|
||||
moneyRecordStore.addMoneyRecord(result)
|
||||
|
||||
router.back()
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SpaceUser } from '@/types/space/spaceUser'
|
||||
import type { SpaceUser } from '@/types/space/space'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import { useState } from 'react'
|
||||
import { FlatList, TouchableHighlight, TouchableOpacity, View } from 'react-native'
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
renderers,
|
||||
} from 'react-native-popup-menu'
|
||||
import QRCode from 'react-native-qrcode-svg'
|
||||
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
import { AAvatar, AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
import { AModal } from '@/components/common/ao/index'
|
||||
import {
|
||||
Accordion,
|
||||
@@ -21,19 +21,14 @@ import {
|
||||
AccordionTitleText,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion'
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallbackText,
|
||||
} from '@/components/ui/avatar'
|
||||
|
||||
import { Badge, BadgeText } from '@/components/ui/badge'
|
||||
import { Heading } from '@/components/ui/heading'
|
||||
import { HStack } from '@/components/ui/hstack'
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@/components/ui/icon'
|
||||
import { Text } from '@/components/ui/text'
|
||||
import { VStack } from '@/components/ui/vstack'
|
||||
import { dissolveSpaceApi } from '@/request/api/space/space'
|
||||
import { exitSpaceApi, removeSpaceUserApi } from '@/request/api/space/spaceUser'
|
||||
import { dissolveSpaceApi, exitSpaceApi, removeSpaceUserApi } from '@/request/api/space/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatRelativeTime } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
@@ -86,7 +81,9 @@ export default function SpaceInvite({
|
||||
}
|
||||
if (modal.type === 'exit') {
|
||||
// 退出
|
||||
exitSpaceApi({}).catch((err) => {
|
||||
exitSpaceApi({
|
||||
space_id: userSpace.id,
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
@@ -96,6 +93,7 @@ export default function SpaceInvite({
|
||||
}
|
||||
// 移除
|
||||
removeSpaceUserApi({
|
||||
space_id: userSpace.id,
|
||||
user_id: modal.userId,
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
@@ -103,7 +101,9 @@ export default function SpaceInvite({
|
||||
}
|
||||
else {
|
||||
// 解散
|
||||
dissolveSpaceApi({}).catch((err) => {
|
||||
dissolveSpaceApi({
|
||||
space_id: userSpace.id,
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export default function SpaceInvite({
|
||||
<ADivider direction="horizontal" text="扫描二维码" />
|
||||
<QRCode
|
||||
size={120}
|
||||
value={userSpace?.invite_code}
|
||||
value={userSpace?.invite_code || ''}
|
||||
/>
|
||||
</View>
|
||||
</AccordionContent>
|
||||
@@ -198,26 +198,21 @@ export default function SpaceInvite({
|
||||
<View>
|
||||
<FlatList
|
||||
data={spaceUsers}
|
||||
keyExtractor={item => item.user_id.toString()}
|
||||
keyExtractor={item => item.id.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity>
|
||||
<HStack space="md" className="my-2 items-center">
|
||||
<Avatar className="bg-indigo-600">
|
||||
<AvatarFallbackText className="text-white text-2xl">
|
||||
{item.username}
|
||||
</AvatarFallbackText>
|
||||
<AvatarBadge />
|
||||
</Avatar>
|
||||
<AAvatar avatar={item.avatar} username={item.username} showBadge={true} />
|
||||
<VStack>
|
||||
<HStack space="sm">
|
||||
<Heading size="sm">{item.username}</Heading>
|
||||
{item.space_role === 0 && (
|
||||
{item.role === 0 && (
|
||||
<Badge size="sm" variant="solid" action="muted">
|
||||
<BadgeText>创建者</BadgeText>
|
||||
</Badge>
|
||||
)}
|
||||
{
|
||||
userStore.userInfo?.id === item.user_id && (
|
||||
userStore.userInfo?.id === item.id && (
|
||||
<Badge size="sm" variant="solid" action="info">
|
||||
<BadgeText>
|
||||
我
|
||||
@@ -226,7 +221,7 @@ export default function SpaceInvite({
|
||||
)
|
||||
}
|
||||
</HStack>
|
||||
<Text size="sm" className="mt-1">{formatRelativeTime(item.active_time)}</Text>
|
||||
<Text size="sm" className="mt-1">{formatRelativeTime(item.last_login_time)}</Text>
|
||||
</VStack>
|
||||
|
||||
{userSpace?.role === 0 && (
|
||||
@@ -253,7 +248,7 @@ export default function SpaceInvite({
|
||||
},
|
||||
}}
|
||||
>
|
||||
{userSpace.role === 0 && userStore.userInfo?.id !== item.user_id && (
|
||||
{userSpace.role === 0 && userStore.userInfo?.id !== item.id && (
|
||||
<MenuOption
|
||||
customStyles={{
|
||||
OptionTouchableComponent: TouchableHighlight,
|
||||
@@ -268,7 +263,7 @@ export default function SpaceInvite({
|
||||
open: true,
|
||||
title: `确定将 ${item.username} 提出空间?`,
|
||||
type: 'remove',
|
||||
userId: item.user_id,
|
||||
userId: item.id,
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { SpaceUser } from '@/types/space/spaceUser'
|
||||
import type { SpaceUser } from '@/types/space/space'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import { createSpaceApi } from '@/request/api/space/space'
|
||||
import { getUserSpaceApi, joinSpaceApi, listSpaceUsersApi } from '@/request/api/space/spaceUser'
|
||||
import { createSpaceApi, joinSpaceApi, listSpaceUsersApi } from '@/request/api/space/space'
|
||||
import { useSpaceStore } from '@/store/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import JoinSpace from './components/JoinSpace'
|
||||
import SpaceHome from './components/SpaceHome'
|
||||
import SpaceInvite from './components/SpaceInvite'
|
||||
@@ -12,71 +12,73 @@ import StepFade from './components/StepFade'
|
||||
|
||||
export default function PagerWithTab() {
|
||||
const spaceStore = useSpaceStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const [headerTitle, setHeaderTitle] = useState('我的空间')
|
||||
const [step, setStep] = useState<'join' | 'invite' | 'home'>('home')
|
||||
const [spaceUsers, setSpaceUsers] = useState<SpaceUser[]>([])
|
||||
|
||||
const fetchData = async () => {
|
||||
await Promise.all([
|
||||
getUserSpaceApi()
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
if (res.data === null) {
|
||||
spaceStore.setUserSpace(null)
|
||||
setStep('home')
|
||||
}
|
||||
else {
|
||||
spaceStore.setUserSpace({
|
||||
space_id: res.data.space_id,
|
||||
space_name: res.data.space_name,
|
||||
role: res.data.role,
|
||||
invite_code: res.data.invite_code,
|
||||
})
|
||||
setStep('invite')
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 提示
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('getUserSpaceApi error:', err)),
|
||||
|
||||
listSpaceUsersApi({})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpaceUsers(res.data.users)
|
||||
}
|
||||
else {
|
||||
// 提示
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('listSpaceUsersApi error:', err)),
|
||||
])
|
||||
// 封装获取空间用户列表并直接更新状态
|
||||
const fetchSpaceUsers = async (spaceId: number) => {
|
||||
try {
|
||||
const res = await listSpaceUsersApi({ space_id: spaceId })
|
||||
if (res.isSuccess) {
|
||||
setSpaceUsers(res.data.users)
|
||||
console.log(res.data.users)
|
||||
}
|
||||
else {
|
||||
console.warn('获取空间用户失败')
|
||||
setSpaceUsers([])
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error('listSpaceUsersApi error:', err)
|
||||
setSpaceUsers([])
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时判断并拉取用户列表
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
const loadSpaceUsers = () => {
|
||||
if (spaceStore.userSpace !== null) {
|
||||
setStep('invite')
|
||||
|
||||
fetchSpaceUsers(spaceStore.userSpace.id)
|
||||
}
|
||||
else {
|
||||
setStep('home')
|
||||
}
|
||||
}
|
||||
|
||||
loadSpaceUsers()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (spaceStore.userSpace) {
|
||||
setHeaderTitle(spaceStore.userSpace.name || '我的空间')
|
||||
}
|
||||
else {
|
||||
setHeaderTitle('我的空间')
|
||||
}
|
||||
}, [spaceStore.userSpace])
|
||||
|
||||
function getSpaceName() {
|
||||
const username = userStore.userInfo?.username
|
||||
return username ? `${username}的空间` : '我的空间'
|
||||
}
|
||||
|
||||
function createSpace() {
|
||||
createSpaceApi({}).then((res) => {
|
||||
createSpaceApi({
|
||||
name: getSpaceName(),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
spaceStore.setUserSpace({
|
||||
space_id: res.data.id,
|
||||
space_name: '',
|
||||
id: res.data.space_id,
|
||||
name: getSpaceName(),
|
||||
role: 0,
|
||||
invite_code: res.data.invite_code,
|
||||
})
|
||||
listSpaceUsersApi({})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpaceUsers(res.data.users)
|
||||
}
|
||||
else {
|
||||
// 提示
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('listSpaceUsersApi error:', err))
|
||||
fetchSpaceUsers(res.data.space_id)
|
||||
setStep('invite')
|
||||
}
|
||||
}).catch((err) => {
|
||||
@@ -89,7 +91,14 @@ export default function PagerWithTab() {
|
||||
invite_code: code,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
fetchData()
|
||||
spaceStore.setUserSpace({
|
||||
id: res.data.space_id,
|
||||
name: res.data.space_name,
|
||||
invite_code: code,
|
||||
role: 0,
|
||||
})
|
||||
setStep('invite')
|
||||
fetchSpaceUsers(res.data.space_id)
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
@@ -98,7 +107,7 @@ export default function PagerWithTab() {
|
||||
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<CustomHeader title="共享空间" />
|
||||
<CustomHeader title={headerTitle} />
|
||||
<View className="flex-1">
|
||||
<StepFade visible={step === 'home'}>
|
||||
<SpaceHome
|
||||
@@ -129,12 +138,13 @@ export default function PagerWithTab() {
|
||||
onExit={(data) => {
|
||||
if (data.type === 'remove' && data.userId !== undefined) {
|
||||
setSpaceUsers(prevUsers =>
|
||||
prevUsers.filter(user => user.user_id !== data.userId),
|
||||
prevUsers.filter(user => user.id !== data.userId),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setStep('home')
|
||||
spaceStore.setUserSpace(null)
|
||||
spaceStore.clear()
|
||||
setSpaceUsers([])
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { StyleProp, ViewStyle } from 'react-native'
|
||||
import React from 'react'
|
||||
import {
|
||||
AvatarBadge,
|
||||
AvatarFallbackText,
|
||||
AvatarImage,
|
||||
Avatar as NativeAvatar,
|
||||
} from '@/components/ui/avatar'
|
||||
|
||||
interface AvatarProps {
|
||||
username?: string
|
||||
avatar?: string
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
||||
showBadge?: boolean
|
||||
className?: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
badgeClassName?: string
|
||||
badgeStyle?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<AvatarProps> = ({
|
||||
username = '',
|
||||
avatar,
|
||||
size,
|
||||
showBadge,
|
||||
className = '',
|
||||
style,
|
||||
badgeClassName,
|
||||
badgeStyle,
|
||||
}) => {
|
||||
return (
|
||||
<NativeAvatar
|
||||
size={size}
|
||||
className={`${!avatar ? 'bg-indigo-600' : ''} ${className}`}
|
||||
style={style}
|
||||
>
|
||||
{avatar
|
||||
? (
|
||||
<AvatarImage source={{ uri: avatar }} />
|
||||
)
|
||||
: (
|
||||
<AvatarFallbackText className="text-white text-2xl">
|
||||
{username[0] ?? ''}
|
||||
</AvatarFallbackText>
|
||||
)}
|
||||
|
||||
{showBadge && (
|
||||
<AvatarBadge className={badgeClassName} style={badgeStyle} />
|
||||
)}
|
||||
</NativeAvatar>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type { ReactNode } from 'react'
|
||||
import type { DimensionValue, ViewStyle } from 'react-native'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Dimensions, StyleSheet, Text, View } from 'react-native'
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
interpolate,
|
||||
runOnJS,
|
||||
@@ -103,24 +102,6 @@ export default function Drawer({
|
||||
opacity: interpolate(progress.value, [0, 1], [0, 0.45], 'clamp'),
|
||||
}))
|
||||
|
||||
/* ================= 手势(只给 bottom) ================= */
|
||||
const pan = Gesture.Pan()
|
||||
.onUpdate((e) => {
|
||||
if (menuPosition === 'bottom' && e.translationY > 0) {
|
||||
drag.value = e.translationY
|
||||
}
|
||||
})
|
||||
.onEnd(() => {
|
||||
if (menuPosition === 'bottom' && drag.value > 120) {
|
||||
progress.value = withTiming(0, { duration }, () => {
|
||||
drag.value = 0
|
||||
runOnJS(hideModal)()
|
||||
})
|
||||
}
|
||||
else {
|
||||
drag.value = withTiming(0)
|
||||
}
|
||||
})
|
||||
|
||||
if (!innerVisible)
|
||||
return null
|
||||
@@ -135,34 +116,32 @@ export default function Drawer({
|
||||
|
||||
{/* Drawer */}
|
||||
<View style={[styles.drawerBox, drawerBox]}>
|
||||
<GestureDetector gesture={pan}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.drawer,
|
||||
drawerStyle,
|
||||
{
|
||||
borderTopLeftRadius: isCircle ? CircleNum : 0,
|
||||
borderTopRightRadius: isCircle ? CircleNum : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{showHeader && (
|
||||
header ?? (
|
||||
<View style={styles.headTitle}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<LucideIcon
|
||||
icon="X"
|
||||
size={22}
|
||||
style={styles.close}
|
||||
onPress={hideModal}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.drawer,
|
||||
drawerStyle,
|
||||
{
|
||||
borderTopLeftRadius: isCircle ? CircleNum : 0,
|
||||
borderTopRightRadius: isCircle ? CircleNum : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{showHeader && (
|
||||
header ?? (
|
||||
<View style={styles.headTitle}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<LucideIcon
|
||||
icon="X"
|
||||
size={22}
|
||||
style={styles.close}
|
||||
onPress={hideModal}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
|
||||
{children}
|
||||
</Animated.View>
|
||||
</GestureDetector>
|
||||
{children}
|
||||
</Animated.View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Avatar } from './Avatar'
|
||||
import Button from './Button'
|
||||
import { Cell, CellGroup } from './Cell'
|
||||
import Checkbox from './Checkbox'
|
||||
import DatePicker from './DatePicker'
|
||||
import Divider from './Divider'
|
||||
|
||||
import Divider from './Divider'
|
||||
import Drawer from './Drawer'
|
||||
import Form, { FormRef } from './Form'
|
||||
import FormItem from './FormItem'
|
||||
@@ -12,9 +13,11 @@ import ListItem from './ListItem'
|
||||
import LucideIcon from './LucideIcon'
|
||||
import Modal from './Modal'
|
||||
import SegmentedTabs from './SegmentedTabs'
|
||||
|
||||
import Tab from './Tab'
|
||||
|
||||
export {
|
||||
Avatar as AAvatar,
|
||||
Button as AButton,
|
||||
Cell as ACell,
|
||||
CellGroup as ACellGroup,
|
||||
|
||||
@@ -2,27 +2,27 @@ import type {
|
||||
DeleteMoneyRecordParams,
|
||||
InsertMoneyRecordParams,
|
||||
InsertMoneyRecordResult,
|
||||
QueryMoneyRecordsParams,
|
||||
QueryMoneyRecordsResult,
|
||||
ListMoneyRecordsParams,
|
||||
ListMoneyRecordsResult,
|
||||
UpdateMoneyRecordParams,
|
||||
} from '@/types/money/record'
|
||||
|
||||
import alovaInstance from '../../index'
|
||||
|
||||
const API_PREFIX = '/money/record'
|
||||
const API_PREFIX = '/money'
|
||||
|
||||
export function queryMoneyRecordsApi(params: QueryMoneyRecordsParams) {
|
||||
return alovaInstance.Post<ResponseData<QueryMoneyRecordsResult>>(`${API_PREFIX}/query`, params)
|
||||
export function listMoneyRecordsApi(params: ListMoneyRecordsParams) {
|
||||
return alovaInstance.Post<ResponseData<ListMoneyRecordsResult>>(`${API_PREFIX}/records`, params)
|
||||
}
|
||||
|
||||
export function insertMoneyRecordApi(params: InsertMoneyRecordParams) {
|
||||
return alovaInstance.Post<ResponseData<InsertMoneyRecordResult>>(`${API_PREFIX}/insert`, params)
|
||||
return alovaInstance.Post<ResponseData<InsertMoneyRecordResult>>(`${API_PREFIX}/record/insert`, params)
|
||||
}
|
||||
|
||||
export function updateMoneyRecordApi(params: UpdateMoneyRecordParams) {
|
||||
return alovaInstance.Post<ResponseData<any>>(`${API_PREFIX}/update`, params)
|
||||
return alovaInstance.Post<ResponseData<any>>(`${API_PREFIX}/record/update`, params)
|
||||
}
|
||||
|
||||
export function deleteMoneyRecordApi(params: DeleteMoneyRecordParams) {
|
||||
return alovaInstance.Post<ResponseData<any>>(`${API_PREFIX}/delete`, params)
|
||||
return alovaInstance.Post<ResponseData<any>>(`${API_PREFIX}/record/delete`, params)
|
||||
}
|
||||
@@ -4,5 +4,5 @@ import alovaInstance from '../index'
|
||||
const API_PREFIX = '/user'
|
||||
|
||||
export function getUserSpaceApi() {
|
||||
return alovaInstance.Post<ResponseData<UserSpace>>(`${API_PREFIX}/space`)
|
||||
return alovaInstance.Get<ResponseData<UserSpace>>(`${API_PREFIX}/space`)
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { Method } from 'alova'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { ListUserSpacesResult } from '@/types/user'
|
||||
import { axiosRequestAdapter } from '@alova/adapter-axios'
|
||||
import { createAlova } from 'alova'
|
||||
import { useNavStore } from '@/store/navigation'
|
||||
import { useSpaceStore } from '@/store/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
@@ -12,8 +10,6 @@ enum HttpCode {
|
||||
Success = 200,
|
||||
Unauthorized = 401,
|
||||
Fail = 500,
|
||||
ErrSpaceRequired = 501,
|
||||
ErrSpaceNoPermission = 502,
|
||||
}
|
||||
|
||||
// 仅保留「无需登录」接口
|
||||
@@ -52,29 +48,15 @@ const alovaInstance = createAlova({
|
||||
useNavStore.getState().navigateToAuth()
|
||||
return Promise.reject(new Error('登录已失效'))
|
||||
}
|
||||
else if (resData.code === HttpCode.ErrSpaceRequired || resData.code === HttpCode.ErrSpaceNoPermission) {
|
||||
if (method.meta?.spaceRetried)
|
||||
return Promise.reject(new Error('space retry failed'))
|
||||
|
||||
method.meta = { ...method.meta, spaceRetried: true }
|
||||
|
||||
const res = await alovaInstance.Post<ResponseData<ListUserSpacesResult>>('/user/spaces').send()
|
||||
if (!res.isSuccess || res.data.spaces.length === 0)
|
||||
return Promise.reject(new Error('没有可用空间,请刷新后再试'))
|
||||
|
||||
const defaultSpace = res.data.spaces[0]
|
||||
|
||||
useSpaceStore.getState().setUserSpace(defaultSpace)
|
||||
|
||||
method.config.headers['X-Space-Id'] = defaultSpace.id
|
||||
return method.send()
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
onError: (error: AxiosError) => {
|
||||
const status = error.status
|
||||
const config = error.config
|
||||
|
||||
console.warn(`[HTTP ${status}] ${config?.method?.toUpperCase()} ${config?.url}`)
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
@@ -125,12 +107,6 @@ const alovaInstance = createAlova({
|
||||
|
||||
// 注入 JWT
|
||||
method.config.headers.Token = userStore.userInfo!.token
|
||||
|
||||
// 注入 space 信息(如果存在)
|
||||
const userSpace = useSpaceStore.getState().userSpace
|
||||
if (userSpace) {
|
||||
method.config.headers['X-Space-Id'] = userSpace.id
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -26,18 +26,20 @@ export const useAppStore = create<AppState>((set, get) => ({
|
||||
const moneyCategoryStore = useMoneyCategoryStore.getState()
|
||||
const spaceStore = useSpaceStore.getState()
|
||||
|
||||
// 如果没登录,跳转到登录页,但不退出初始化
|
||||
if (!userStore.isLoggedIn()) {
|
||||
navStore.navigateToAuth()
|
||||
set({ ready: true })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 分类初始化
|
||||
// 初始化分类
|
||||
await moneyCategoryStore.init()
|
||||
|
||||
// 获取空间信息
|
||||
const res = await getUserSpaceApi()
|
||||
console.log(res.data)
|
||||
|
||||
spaceStore.setUserSpace(res.data)
|
||||
}
|
||||
catch (err) {
|
||||
@@ -47,6 +49,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
||||
// 初始化完成
|
||||
set({ ready: true })
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
set({ ready: false })
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { MoneyRecord, MoneyRecordGroup } 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'
|
||||
@@ -18,10 +17,6 @@ interface MoneyRecordStore {
|
||||
setRecords: (records: MoneyRecord[], month: string) => void
|
||||
setCurrentMonth: (month: string) => void
|
||||
|
||||
addMoneyRecord: (data: MoneyRecord) => void
|
||||
updateMoneyRecord: (updated: MoneyRecord) => void
|
||||
deleteMoneyRecord: (id: number) => void
|
||||
|
||||
getMoneyRecordById: (id: number) => MoneyRecord | null
|
||||
}
|
||||
|
||||
@@ -86,33 +81,6 @@ export const useMoneyRecordStore = create<MoneyRecordStore>((set, get) => ({
|
||||
|
||||
setCurrentMonth: month => set({ currentMonth: month }),
|
||||
|
||||
/* ---------------- actions ---------------- */
|
||||
addMoneyRecord: (data) => {
|
||||
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) => {
|
||||
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) => {
|
||||
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) => {
|
||||
for (const group of get().records) {
|
||||
const record = group.records.find(r => r.id === id)
|
||||
|
||||
@@ -6,7 +6,7 @@ interface SpaceState {
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
export const useSpaceStore = create<SpaceState>(set => ({
|
||||
export const useSpaceStore = create<SpaceState>((set, get) => ({
|
||||
userSpace: null,
|
||||
setUserSpace: space => set({ userSpace: space }),
|
||||
clear: () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface MoneyRecord {
|
||||
}
|
||||
|
||||
// 查询条件(请求参数)
|
||||
export interface QueryMoneyRecordsParams {
|
||||
export interface ListMoneyRecordsParams {
|
||||
record_time: string
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface MoneyRecordGroup {
|
||||
records: MoneyRecord[]
|
||||
}
|
||||
|
||||
export interface QueryMoneyRecordsResult {
|
||||
export interface ListMoneyRecordsResult {
|
||||
records: MoneyRecord[]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface InsertMoneyRecordParams {
|
||||
record_time: string
|
||||
}
|
||||
|
||||
export interface InsertMoneyRecordResult extends MoneyRecord {}
|
||||
export interface InsertMoneyRecordResult {}
|
||||
|
||||
// 更新
|
||||
export interface UpdateMoneyRecordParams {
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface UpdateSpaceUserRoleResult {
|
||||
// 空间用户列表
|
||||
export interface SpaceUser {
|
||||
id: number
|
||||
name: number
|
||||
username: string
|
||||
avatar: string
|
||||
role: number
|
||||
last_login_time: number
|
||||
|
||||
Reference in new issue
Block a user