u
This commit is contained in:
1 parent
1445ed3b6c
commit
7419021927
19 files changed
+474
-327
No files matched your search
@@ -42,11 +42,13 @@
|
|||||||
"react-native-gesture-handler": "~2.28.0",
|
"react-native-gesture-handler": "~2.28.0",
|
||||||
"react-native-keyboard-controller": "1.18.5",
|
"react-native-keyboard-controller": "1.18.5",
|
||||||
"react-native-modal": "14.0.0-rc.1",
|
"react-native-modal": "14.0.0-rc.1",
|
||||||
|
"react-native-pager-view": "6.9.1",
|
||||||
"react-native-popup-menu": "^0.18.0",
|
"react-native-popup-menu": "^0.18.0",
|
||||||
"react-native-reanimated": "~4.1.3",
|
"react-native-reanimated": "~4.1.3",
|
||||||
"react-native-safe-area-context": "~5.6.1",
|
"react-native-safe-area-context": "~5.6.1",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-svg": "15.12.1",
|
"react-native-svg": "15.12.1",
|
||||||
|
"react-native-tab-view": "^4.2.0",
|
||||||
"react-native-web": "^0.21.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-worklets": "0.5.1",
|
"react-native-worklets": "0.5.1",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
|||||||
+31
@@ -104,6 +104,9 @@ importers:
|
|||||||
react-native-modal:
|
react-native-modal:
|
||||||
specifier: 14.0.0-rc.1
|
specifier: 14.0.0-rc.1
|
||||||
version: 14.0.0-rc.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 14.0.0-rc.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
react-native-pager-view:
|
||||||
|
specifier: 6.9.1
|
||||||
|
version: 6.9.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-popup-menu:
|
react-native-popup-menu:
|
||||||
specifier: ^0.18.0
|
specifier: ^0.18.0
|
||||||
version: 0.18.0
|
version: 0.18.0
|
||||||
@@ -119,6 +122,9 @@ importers:
|
|||||||
react-native-svg:
|
react-native-svg:
|
||||||
specifier: 15.12.1
|
specifier: 15.12.1
|
||||||
version: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
react-native-tab-view:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0(react-native-pager-view@6.9.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
react-native-web:
|
react-native-web:
|
||||||
specifier: ^0.21.0
|
specifier: ^0.21.0
|
||||||
version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -4857,6 +4863,12 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '>=0.70.0'
|
react-native: '>=0.70.0'
|
||||||
|
|
||||||
|
react-native-pager-view@6.9.1:
|
||||||
|
resolution: {integrity: sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
|
||||||
react-native-popup-menu@0.18.0:
|
react-native-popup-menu@0.18.0:
|
||||||
resolution: {integrity: sha512-XD3gyp/hwL5YWw2J034DerfvbJ6S2YHqAzvqAFRU+UGnFD4JyTqggfm4UyynBD26O1PDoRb45Loil+NZCGfCnA==}
|
resolution: {integrity: sha512-XD3gyp/hwL5YWw2J034DerfvbJ6S2YHqAzvqAFRU+UGnFD4JyTqggfm4UyynBD26O1PDoRb45Loil+NZCGfCnA==}
|
||||||
|
|
||||||
@@ -4892,6 +4904,13 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
react-native-tab-view@4.2.0:
|
||||||
|
resolution: {integrity: sha512-TUbh7Yr0tE/99t1pJQLbQ+4/Px67xkT7/r3AhfV+93Q3WoUira0Lx7yuKUP2C118doqxub8NCLERwcqsHr29nQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 18.2.0'
|
||||||
|
react-native: '*'
|
||||||
|
react-native-pager-view: '>= 6.0.0'
|
||||||
|
|
||||||
react-native-web@0.21.2:
|
react-native-web@0.21.2:
|
||||||
resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
|
resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -11796,6 +11815,11 @@ snapshots:
|
|||||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
react-native-animatable: 1.4.0
|
react-native-animatable: 1.4.0
|
||||||
|
|
||||||
|
react-native-pager-view@6.9.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
react-native-popup-menu@0.18.0: {}
|
react-native-popup-menu@0.18.0: {}
|
||||||
|
|
||||||
react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
@@ -11840,6 +11864,13 @@ snapshots:
|
|||||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
warn-once: 0.1.1
|
warn-once: 0.1.1
|
||||||
|
|
||||||
|
react-native-tab-view@4.2.0(react-native-pager-view@6.9.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
react-native-pager-view: 6.9.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
use-latest-callback: 0.2.6(react@19.1.0)
|
||||||
|
|
||||||
react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
|
|||||||
@@ -1,39 +1,125 @@
|
|||||||
import { DatePicker } from '@yz1311/react-native-wheel-picker'
|
import * as React from 'react'
|
||||||
import { useState } from 'react'
|
import { Pressable, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
|
||||||
import { Button, Text, View } from 'react-native'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import { ADatePicker } from '@/components/common/ao'
|
import { TabView } from 'react-native-tab-view'
|
||||||
|
|
||||||
export default function Chat() {
|
// 两个示例页面
|
||||||
const [show, setShow] = useState(false)
|
function PageOne() {
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={styles.page}>
|
||||||
<View className="mt-12">
|
<Text style={{ fontSize: 18 }}>第一页内容</Text>
|
||||||
<Button
|
</View>
|
||||||
title="测试"
|
|
||||||
onPress={() => {
|
|
||||||
setShow(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</Button>
|
|
||||||
{/* <DatePicker
|
|
||||||
isModal={true}
|
|
||||||
modalVisible={show}
|
|
||||||
onPickerCancel={() => {
|
|
||||||
setShow(false)
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
<ADatePicker
|
|
||||||
show={show}
|
|
||||||
onConfirm={(val) => {
|
|
||||||
setShow(false)
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
setShow(false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PageTwo() {
|
||||||
|
return (
|
||||||
|
<View style={styles.page}>
|
||||||
|
<Text style={{ fontSize: 18 }}>第二页内容</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SimpleTabView() {
|
||||||
|
const layout = useWindowDimensions()
|
||||||
|
const [index, setIndex] = React.useState(0)
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ key: 'one', title: 'Tab 1' },
|
||||||
|
{ key: 'two', title: 'Tab 2' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const renderScene = ({ route }: { route: { key: string } }) => {
|
||||||
|
switch (route.key) {
|
||||||
|
case 'one':
|
||||||
|
return <PageOne />
|
||||||
|
case 'two':
|
||||||
|
return <PageTwo />
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
|
||||||
|
{/* Header + Tab */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Pressable onPress={() => console.log('Left button clicked')}>
|
||||||
|
<Text style={styles.headerButton}>返回</Text>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<View style={styles.tabContainer}>
|
||||||
|
{routes.map((r, i) => {
|
||||||
|
const focused = i === index
|
||||||
|
return (
|
||||||
|
<Pressable key={r.key} onPress={() => setIndex(i)} style={styles.tabButton}>
|
||||||
|
<Text style={[styles.tabText, focused && styles.tabTextActive]}>{r.title}</Text>
|
||||||
|
{focused && <View style={styles.underline} />}
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Pressable onPress={() => console.log('Right button clicked')}>
|
||||||
|
<Text style={styles.headerButton}>添加</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* TabView */}
|
||||||
|
<TabView
|
||||||
|
navigationState={{ index, routes }}
|
||||||
|
renderScene={renderScene}
|
||||||
|
onIndexChange={setIndex}
|
||||||
|
initialLayout={{ width: layout.width }}
|
||||||
|
renderTabBar={() => null} // 不使用默认 TabBar
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View><Text>123</Text></View>
|
||||||
|
</SafeAreaView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderColor: '#ddd',
|
||||||
|
},
|
||||||
|
headerButton: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#007AFF',
|
||||||
|
},
|
||||||
|
tabContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
tabButton: {
|
||||||
|
marginHorizontal: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 4,
|
||||||
|
},
|
||||||
|
tabText: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
tabTextActive: {
|
||||||
|
color: '#007AFF',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
height: 2,
|
||||||
|
backgroundColor: '#007AFF',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,78 +1,18 @@
|
|||||||
import React, { useState } from 'react'
|
import { useRouter } from 'expo-router'
|
||||||
import { FlatList, Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
import {
|
import { AButton } from '@/components/common/ao'
|
||||||
Menu,
|
|
||||||
MenuOption,
|
|
||||||
MenuOptions,
|
|
||||||
MenuProvider,
|
|
||||||
MenuTrigger,
|
|
||||||
} from 'react-native-popup-menu'
|
|
||||||
import { Button, ButtonText } from '@/components/ui/button'
|
|
||||||
|
|
||||||
export default function Example() {
|
export default function Love() {
|
||||||
const data = Array.from({ length: 10 }, (_, i) => i + 1)
|
const router = useRouter()
|
||||||
const [activeIndex, setActiveIndex] = useState<number | null>(null)
|
|
||||||
|
|
||||||
const renderItem = ({ item: index }: { item: number }) => {
|
|
||||||
const isActive = activeIndex === index
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
marginVertical: 10,
|
|
||||||
padding: 10,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#ccc',
|
|
||||||
borderRadius: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu
|
|
||||||
onOpen={() => setActiveIndex(index)}
|
|
||||||
onClose={() => setActiveIndex(null)}
|
|
||||||
>
|
|
||||||
{/* MenuTrigger 应该包装一个可触摸的组件 */}
|
|
||||||
<MenuTrigger
|
|
||||||
customStyles={{
|
|
||||||
TriggerTouchableComponent: Button, // 使用你的 Button 组件
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 触发区域的内容 */}
|
|
||||||
<ButtonText>
|
|
||||||
Item
|
|
||||||
{' '}
|
|
||||||
{index}
|
|
||||||
{' '}
|
|
||||||
{isActive ? '(active)' : ''}
|
|
||||||
</ButtonText>
|
|
||||||
</MenuTrigger>
|
|
||||||
|
|
||||||
<MenuOptions>
|
|
||||||
<MenuOption onSelect={() => alert(`Clicked Option A for ${index}`)}>
|
|
||||||
<Text>
|
|
||||||
Option A -
|
|
||||||
{index}
|
|
||||||
</Text>
|
|
||||||
</MenuOption>
|
|
||||||
<MenuOption onSelect={() => alert(`Clicked Option B for ${index}`)}>
|
|
||||||
<Text style={{ color: 'red' }}>
|
|
||||||
Option B -
|
|
||||||
{index}
|
|
||||||
</Text>
|
|
||||||
</MenuOption>
|
|
||||||
<MenuOption onSelect={() => setActiveIndex(null)}>
|
|
||||||
<Text>Close Menu</Text>
|
|
||||||
</MenuOption>
|
|
||||||
</MenuOptions>
|
|
||||||
</Menu>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<View className="mt-20">
|
||||||
data={data}
|
<AButton onPress={() => {
|
||||||
renderItem={renderItem}
|
router.navigate('/auth')
|
||||||
keyExtractor={item => item.toString()}
|
}}
|
||||||
/>
|
>
|
||||||
|
跳转到登录页面
|
||||||
|
</AButton>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MoneyCategoryItem, QueryMoneyCategoryResult } from '@/types/money/category'
|
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||||
import type { MoneyRecordBase, MoneyRecordItem } from '@/types/money/record'
|
import type { MoneyRecordBase, MoneyRecordItem } from '@/types/money/record'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
|
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
|
||||||
import { queryMoneyCategoryApi } from '@/request/api/money/category'
|
import { queryMoneyCategoryApi } from '@/request/api/money/category'
|
||||||
import { insertMoneyRecordApi, updateMoneyRecordApi } from '@/request/api/money/record'
|
import { insertMoneyRecordApi, updateMoneyRecordApi } from '@/request/api/money/record'
|
||||||
|
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||||
import { getCurrentDate, toDateString } from '@/utils/time'
|
import { getCurrentDate, toDateString } from '@/utils/time'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
||||||
@@ -65,6 +66,8 @@ export default function AddMoneyDrawer({
|
|||||||
onConfirm,
|
onConfirm,
|
||||||
updateData,
|
updateData,
|
||||||
}: AddMoneyDrawerProps) {
|
}: AddMoneyDrawerProps) {
|
||||||
|
const moneyCategoryStore = useMoneyCategoryStore()
|
||||||
|
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
// 选择顺序:0 支出 1收入
|
// 选择顺序:0 支出 1收入
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
@@ -76,11 +79,6 @@ export default function AddMoneyDrawer({
|
|||||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||||
// 备注聚焦
|
// 备注聚焦
|
||||||
const [remarkIsFocused, setRemarkIsFocused] = useState(false)
|
const [remarkIsFocused, setRemarkIsFocused] = useState(false)
|
||||||
// 分类
|
|
||||||
const [categories, setCategories] = useState<QueryMoneyCategoryResult>({
|
|
||||||
income: [],
|
|
||||||
expense: [],
|
|
||||||
})
|
|
||||||
// 选中分类
|
// 选中分类
|
||||||
const [selectedCategory, setSelectedCategory] = useState<MoneyCategoryItem | null>(null)
|
const [selectedCategory, setSelectedCategory] = useState<MoneyCategoryItem | null>(null)
|
||||||
|
|
||||||
@@ -106,10 +104,13 @@ export default function AddMoneyDrawer({
|
|||||||
|
|
||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await queryMoneyCategoryApi()
|
if (moneyCategoryStore.categories.length === 0) {
|
||||||
if (res.isSuccess) {
|
const res = await queryMoneyCategoryApi()
|
||||||
setCategories(res.data)
|
if (res.isSuccess) {
|
||||||
|
moneyCategoryStore.setCategories(res.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
console.log(moneyCategoryStore.categories)
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error('获取分类失败', err)
|
console.error('获取分类失败', err)
|
||||||
@@ -128,7 +129,7 @@ export default function AddMoneyDrawer({
|
|||||||
|
|
||||||
if (type === 0) {
|
if (type === 0) {
|
||||||
// 支出
|
// 支出
|
||||||
categories.expense.forEach((item) => {
|
moneyCategoryStore.expense.forEach((item) => {
|
||||||
if (item.id === updateData.category_id) {
|
if (item.id === updateData.category_id) {
|
||||||
setSelectedCategory(item)
|
setSelectedCategory(item)
|
||||||
}
|
}
|
||||||
@@ -136,14 +137,14 @@ export default function AddMoneyDrawer({
|
|||||||
}
|
}
|
||||||
else if (type === 1) {
|
else if (type === 1) {
|
||||||
// 收入
|
// 收入
|
||||||
categories.income.forEach((item) => {
|
moneyCategoryStore.income.forEach((item) => {
|
||||||
if (item.id === updateData.category_id) {
|
if (item.id === updateData.category_id) {
|
||||||
setSelectedCategory(item)
|
setSelectedCategory(item)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [categories])
|
}, [isOpen])
|
||||||
|
|
||||||
function resetData() {
|
function resetData() {
|
||||||
// 清空键盘记录
|
// 清空键盘记录
|
||||||
@@ -399,7 +400,6 @@ export default function AddMoneyDrawer({
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
|
|
||||||
const result: MoneyRecordItem = {
|
const result: MoneyRecordItem = {
|
||||||
...res.data,
|
...res.data,
|
||||||
category_name: selectedCategory.name,
|
category_name: selectedCategory.name,
|
||||||
@@ -466,9 +466,8 @@ export default function AddMoneyDrawer({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(() => {
|
{(() => {
|
||||||
const list = selectedIndex === 1 ? categories.income : categories.expense
|
const list = selectedIndex === 1 ? moneyCategoryStore.income : moneyCategoryStore.expense
|
||||||
|
|
||||||
// useMemo 生成补全后的列表
|
|
||||||
const memoItems = useMemo(() => {
|
const memoItems = useMemo(() => {
|
||||||
const items = [...list]
|
const items = [...list]
|
||||||
const remainder = items.length % 4
|
const remainder = items.length % 4
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from 'react-native-popup-menu'
|
} from 'react-native-popup-menu'
|
||||||
import { LucideIcon } from '@/components/common/ao'
|
import { LucideIcon } from '@/components/common/ao'
|
||||||
import EmptyState from '@/components/common/EmptyStatus'
|
import EmptyState from '@/components/common/EmptyStatus'
|
||||||
import { Badge, BadgeText } from '@/components/ui/badge'
|
|
||||||
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
import MoneyCategoryIcon from './MoneyCategoryIcon'
|
||||||
|
|
||||||
interface StaticTransactionsProps {
|
interface StaticTransactionsProps {
|
||||||
@@ -112,7 +111,7 @@ export default function StaticTransactions({
|
|||||||
{
|
{
|
||||||
index === 1 && (
|
index === 1 && (
|
||||||
<Text className="text-gray-500 text-xs italic">
|
<Text className="text-gray-500 text-xs italic">
|
||||||
操作人:张三
|
张三
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function ListItem({ icon, text, href, onPress }: ListItemProps) {
|
|||||||
className="flex flex-row items-center py-3"
|
className="flex flex-row items-center py-3"
|
||||||
onPress={handlePress}
|
onPress={handlePress}
|
||||||
>
|
>
|
||||||
<LucideIcon icon={icon} size={24} strokeWidth={1.5} />
|
<LucideIcon icon={icon} size={20} strokeWidth={1.5} />
|
||||||
|
|
||||||
<Text className="text-base ml-3">{text}</Text>
|
<Text className="text-base ml-3">{text}</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { StepLayoutItem } from '@/components/custom/StepLayout'
|
|
||||||
import { Slot, Stack, useRouter } from 'expo-router'
|
|
||||||
import { Image, Text, View } from 'react-native'
|
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
||||||
import StepLayout from '@/components/custom/StepLayout'
|
|
||||||
import CustomTabbar from '@/components/custom/Tabbar'
|
|
||||||
|
|
||||||
const steps: StepLayoutItem[] = [
|
|
||||||
{
|
|
||||||
path: '/auth/index',
|
|
||||||
showHearder: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '登录',
|
|
||||||
path: '/auth/login',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '注册',
|
|
||||||
path: '/auth/register',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function AuthLayout() {
|
|
||||||
return (
|
|
||||||
<StepLayout steps={steps}>
|
|
||||||
<Slot />
|
|
||||||
</StepLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useRouter } from 'expo-router'
|
import { useRouter } from 'expo-router'
|
||||||
import { Image, Text, View } from 'react-native'
|
import { Image, Text, View } from 'react-native'
|
||||||
|
import CustomHeader from '@/components/custom/Header'
|
||||||
import { Button, ButtonText } from '@/components/ui/button'
|
import { Button, ButtonText } from '@/components/ui/button'
|
||||||
|
|
||||||
export default function AuthIndex() {
|
export default function AuthIndex() {
|
||||||
@@ -10,33 +11,39 @@ export default function AuthIndex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-white items-center justify-center">
|
<View className="flex-1">
|
||||||
<Image
|
<CustomHeader
|
||||||
source={require('@/static/images/work.png')}
|
showHeader={false}
|
||||||
style={{ width: '100%', height: 280 }}
|
|
||||||
className="mb-6"
|
|
||||||
/>
|
/>
|
||||||
|
<View className="flex-1 bg-white items-center justify-center">
|
||||||
|
<Image
|
||||||
|
source={require('@/static/images/work.png')}
|
||||||
|
style={{ width: '100%', height: 280 }}
|
||||||
|
className="mb-6"
|
||||||
|
/>
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
source={require('@/static/images/logo.png')}
|
source={require('@/static/images/logo.png')}
|
||||||
style={{ width: 60, height: 60 }}
|
style={{ width: 60, height: 60 }}
|
||||||
className="mb-6"
|
className="mb-6"
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text className="font-bold text-3xl text-[#333] tracking-wide">
|
<Text className="font-bold text-3xl text-[#333] tracking-wide">
|
||||||
ONEAO
|
ONEAO
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View className="w-full flex flex-col gap-3 mt-12 px-3">
|
<View className="w-full flex flex-col gap-3 mt-12 px-3">
|
||||||
<Button onPress={() => toPath('/auth/login')}>
|
<Button onPress={() => toPath('/auth/login')}>
|
||||||
<ButtonText>登录</ButtonText>
|
<ButtonText>登录</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button action="secondary" onPress={() => toPath('/auth/register')}>
|
<Button action="secondary" onPress={() => toPath('/auth/register')}>
|
||||||
<ButtonText>注册</ButtonText>
|
<ButtonText>注册</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useRouter } from 'expo-router'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Image, Text, View } from 'react-native'
|
import { Image, Text, View } from 'react-native'
|
||||||
import { AButton, ACheckbox, AForm, AFormItem, AInput } from '@/components/common/ao'
|
import { AButton, ACheckbox, AForm, AFormItem, AInput } from '@/components/common/ao'
|
||||||
|
import CustomHeader from '@/components/custom/Header'
|
||||||
import { IconQq, IconWeixin, IconZhiwen } from '@/components/iconfont'
|
import { IconQq, IconWeixin, IconZhiwen } from '@/components/iconfont'
|
||||||
import { Divider } from '@/components/ui/divider'
|
import { Divider } from '@/components/ui/divider'
|
||||||
import useFormValidation from '@/hooks/useFormValidation'
|
import useFormValidation from '@/hooks/useFormValidation'
|
||||||
@@ -85,59 +86,63 @@ export default function AuthLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex flex-col items-center px-6 h-full">
|
<View className="flex-1">
|
||||||
<Image
|
<CustomHeader title="登录" />
|
||||||
source={require('@/static/images/logo.png')}
|
<View className="flex flex-col items-center px-6 h-full">
|
||||||
style={{ width: 120, height: 120 }}
|
<Image
|
||||||
className="my-12"
|
source={require('@/static/images/logo.png')}
|
||||||
/>
|
style={{ width: 120, height: 120 }}
|
||||||
|
className="my-12"
|
||||||
<AForm form={form}>
|
|
||||||
<AFormItem label="账号">
|
|
||||||
<AInput
|
|
||||||
value={form.account}
|
|
||||||
onChangeText={text => handleInputChange('account', text)}
|
|
||||||
/>
|
|
||||||
</AFormItem>
|
|
||||||
|
|
||||||
<AFormItem label="密码">
|
|
||||||
<AInput
|
|
||||||
isPassword
|
|
||||||
value={form.password}
|
|
||||||
onChangeText={text => handleInputChange('password', text)}
|
|
||||||
/>
|
|
||||||
</AFormItem>
|
|
||||||
</AForm>
|
|
||||||
|
|
||||||
<View className="w-full flex-row justify-between items-center mb-6">
|
|
||||||
<ACheckbox
|
|
||||||
isChecked={form.rememberMe}
|
|
||||||
onChange={checked => setForm(prev => ({ ...prev, rememberMe: checked }))}
|
|
||||||
label="记住我"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text className="text-blue-500">忘记密码?</Text>
|
<AForm form={form}>
|
||||||
</View>
|
<AFormItem label="账号">
|
||||||
|
<AInput
|
||||||
|
value={form.account}
|
||||||
|
onChangeText={text => handleInputChange('account', text)}
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
|
||||||
<AButton className="w-full" onPress={loginDefault} disabled={!isValid} loading={loading}>
|
<AFormItem label="密码">
|
||||||
登录
|
<AInput
|
||||||
</AButton>
|
isPassword
|
||||||
|
value={form.password}
|
||||||
|
onChangeText={text => handleInputChange('password', text)}
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
</AForm>
|
||||||
|
|
||||||
<View className="flex-row items-center my-8 w-full">
|
<View className="w-full flex-row justify-between items-center mb-6">
|
||||||
<View className="flex-1">
|
<ACheckbox
|
||||||
<Divider />
|
isChecked={form.rememberMe}
|
||||||
|
onChange={checked => setForm(prev => ({ ...prev, rememberMe: checked }))}
|
||||||
|
label="记住我"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text className="text-blue-500">忘记密码?</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className="mx-3 text-gray-500 text-sm">社交账号</Text>
|
|
||||||
<View className="flex-1">
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="w-full flex-row justify-between items-center">
|
<AButton className="w-full" onPress={loginDefault} disabled={!isValid} loading={loading}>
|
||||||
<IconQq size={32} onPress={loginQq} />
|
登录
|
||||||
<IconWeixin size={32} onPress={loginWx} />
|
</AButton>
|
||||||
<IconZhiwen size={32} onPress={loginZhiwen} />
|
|
||||||
|
<View className="flex-row items-center my-8 w-full">
|
||||||
|
<View className="flex-1">
|
||||||
|
<Divider />
|
||||||
|
</View>
|
||||||
|
<Text className="mx-3 text-gray-500 text-sm">社交账号</Text>
|
||||||
|
<View className="flex-1">
|
||||||
|
<Divider />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="w-full flex-row justify-between items-center">
|
||||||
|
<IconQq size={32} onPress={loginQq} />
|
||||||
|
<IconWeixin size={32} onPress={loginWx} />
|
||||||
|
<IconZhiwen size={32} onPress={loginZhiwen} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import { useRef, useState } from 'react'
|
|||||||
|
|
||||||
import { Image, ScrollView, View } from 'react-native'
|
import { Image, ScrollView, View } from 'react-native'
|
||||||
import { AButton, AForm, AFormItem, AInput } from '@/components/common/ao'
|
import { AButton, AForm, AFormItem, AInput } from '@/components/common/ao'
|
||||||
|
import CustomHeader from '@/components/custom/Header'
|
||||||
import { registerDefaultApi } from '@/request/api/auth'
|
import { registerDefaultApi } from '@/request/api/auth'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
@@ -57,68 +58,72 @@ export default function AuthRegister() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
<View className="flex-1">
|
||||||
<View className="flex flex-col items-center px-6 bg-white ">
|
<CustomHeader title="注册" />
|
||||||
<Image
|
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
||||||
source={require('@/static/images/logo.png')}
|
<View className="flex flex-col items-center px-6 bg-white ">
|
||||||
style={{ width: 120, height: 120 }}
|
<Image
|
||||||
className="my-12"
|
source={require('@/static/images/logo.png')}
|
||||||
/>
|
style={{ width: 120, height: 120 }}
|
||||||
|
className="my-12"
|
||||||
|
/>
|
||||||
|
|
||||||
<AForm ref={formRef} form={form} scrollRef={scrollRef}>
|
<AForm ref={formRef} form={form} scrollRef={scrollRef}>
|
||||||
<AFormItem
|
<AFormItem
|
||||||
name="username"
|
name="username"
|
||||||
label="账号"
|
label="账号"
|
||||||
isRequired
|
isRequired
|
||||||
>
|
>
|
||||||
<AInput
|
<AInput
|
||||||
placeholder="请输入账号"
|
placeholder="请输入账号"
|
||||||
leftIcon={UserRound}
|
leftIcon={UserRound}
|
||||||
value={form.username}
|
value={form.username}
|
||||||
onChangeText={text => handleInputChange('username', text)}
|
onChangeText={text => handleInputChange('username', text)}
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
||||||
<AFormItem
|
<AFormItem
|
||||||
name="password"
|
name="password"
|
||||||
label="密码"
|
label="密码"
|
||||||
isRequired
|
isRequired
|
||||||
>
|
>
|
||||||
<AInput
|
<AInput
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
leftIcon={Lock}
|
leftIcon={Lock}
|
||||||
isPassword
|
isPassword
|
||||||
value={form.password}
|
value={form.password}
|
||||||
onChangeText={text => handleInputChange('password', text)}
|
onChangeText={text => handleInputChange('password', text)}
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
||||||
<AFormItem
|
<AFormItem
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
label="确认密码"
|
label="确认密码"
|
||||||
isRequired
|
isRequired
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
validator: val => val === form.password,
|
validator: val => val === form.password,
|
||||||
message: '两次密码不一致',
|
message: '两次密码不一致',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<AInput
|
<AInput
|
||||||
placeholder="请再次输入密码"
|
placeholder="请再次输入密码"
|
||||||
leftIcon={Lock}
|
leftIcon={Lock}
|
||||||
isPassword
|
isPassword
|
||||||
value={form.confirmPassword}
|
value={form.confirmPassword}
|
||||||
onChangeText={text => handleInputChange('confirmPassword', text)}
|
onChangeText={text => handleInputChange('confirmPassword', text)}
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
</AForm>
|
</AForm>
|
||||||
|
|
||||||
<AButton className="w-full mb-4" onPress={handleRegister} loading={loading}>
|
<AButton className="w-full mb-4" onPress={handleRegister} loading={loading}>
|
||||||
|
|
||||||
|
注册
|
||||||
|
</AButton>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
注册
|
|
||||||
</AButton>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
|
import CustomHeader from '@/components/custom/Header'
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Share() {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View className="flex-1">
|
||||||
<Text>Settings</Text>
|
<CustomHeader title="设置" />
|
||||||
|
|
||||||
|
<View className="flex-1">
|
||||||
|
<Text>123</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
import { Stack } from 'expo-router'
|
import { Stack } from 'expo-router'
|
||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
|
import CustomHeader from '@/components/custom/Header'
|
||||||
|
|
||||||
export default function Share() {
|
export default function Share() {
|
||||||
return (
|
return (
|
||||||
<>
|
<View className="flex-1">
|
||||||
<Stack.Screen options={{
|
<CustomHeader title="共享空间" />
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View>
|
|
||||||
<Text>Share</Text>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
|
|
||||||
|
<View className="flex-1"></View>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,92 @@
|
|||||||
import { Stack } from 'expo-router'
|
import type { ViewStyle } from 'react-native'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { Stack, useRouter } from 'expo-router'
|
||||||
|
import React from 'react'
|
||||||
|
import { Image, Text, TouchableOpacity, View } from 'react-native'
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
import { LucideIcon } from '../common/ao'
|
||||||
|
|
||||||
|
export type StackScreenProps = React.ComponentProps<typeof Stack.Screen>
|
||||||
|
|
||||||
|
interface CustomHeaderProps {
|
||||||
|
showHeader?: boolean
|
||||||
|
title?: string | React.ReactNode
|
||||||
|
left?: React.ReactNode
|
||||||
|
right?: React.ReactNode
|
||||||
|
safeArea?: boolean
|
||||||
|
screenProps?: StackScreenProps
|
||||||
|
className?: string
|
||||||
|
style?: ViewStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomHeader({
|
||||||
|
showHeader = true,
|
||||||
|
title,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
safeArea = true,
|
||||||
|
className = '',
|
||||||
|
style,
|
||||||
|
screenProps,
|
||||||
|
}: CustomHeaderProps) {
|
||||||
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
if (!showHeader) {
|
||||||
|
return (
|
||||||
|
<Stack.Screen
|
||||||
|
{...screenProps}
|
||||||
|
options={{
|
||||||
|
...screenProps?.options,
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function CustomHeader() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack.Screen options={{
|
<Stack.Screen
|
||||||
headerShown: false,
|
{...screenProps}
|
||||||
}}
|
options={{
|
||||||
|
...screenProps?.options,
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SafeAreaView className='flex-1'>
|
<View
|
||||||
|
className={`flex-row items-center h-24 px-2 ${className}`}
|
||||||
</SafeAreaView>
|
style={[
|
||||||
|
{ paddingTop: safeArea ? safeAreaInsets.top : 0 },
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View className="w-10 h-full justify-center">
|
||||||
|
{left || (
|
||||||
|
<TouchableOpacity onPress={() => router.back()}>
|
||||||
|
<LucideIcon icon="ArrowLeft" size={24} clickable={false} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="flex-1 items-center justify-center">
|
||||||
|
{typeof title === 'string'
|
||||||
|
? (
|
||||||
|
<Text className="text-xl">{title}</Text>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
title
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="w-10 h-full items-center justify-center">
|
||||||
|
{right || (
|
||||||
|
<Image
|
||||||
|
source={require('@/static/images/logo.png')}
|
||||||
|
style={{ width: 22, height: 22 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -30,8 +30,8 @@ function isWhiteListed(url: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const alovaInstance = createAlova({
|
const alovaInstance = createAlova({
|
||||||
// baseURL: 'http://192.168.5.6:8085/app',
|
baseURL: 'http://192.168.5.6:8085/app',
|
||||||
baseURL: 'http://192.168.1.136:8085/app',
|
// baseURL: 'http://192.168.1.136:8085/app',
|
||||||
requestAdapter: axiosRequestAdapter(),
|
requestAdapter: axiosRequestAdapter(),
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
cacheFor: null,
|
cacheFor: null,
|
||||||
@@ -76,9 +76,9 @@ const alovaInstance = createAlova({
|
|||||||
|
|
||||||
// 重试原请求
|
// 重试原请求
|
||||||
method.config.headers.token = newToken
|
method.config.headers.token = newToken
|
||||||
const retryResult = await method.send() // ✅ 获取结果
|
const retryResult = await method.send()
|
||||||
|
|
||||||
return retryResult // ✅ 一定要 return!
|
return retryResult
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
@@ -88,7 +88,6 @@ const alovaInstance = createAlova({
|
|||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError, _method) => {
|
onError: (error: AxiosError, _method) => {
|
||||||
console.log('===跳转到error了===')
|
|
||||||
console.log(error.status)
|
console.log(error.status)
|
||||||
|
|
||||||
switch (error.status) {
|
switch (error.status) {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||||
|
import { create } from 'zustand'
|
||||||
|
|
||||||
|
interface MoneyCategoryStore {
|
||||||
|
categories: MoneyCategoryItem[]
|
||||||
|
expense: MoneyCategoryItem[]
|
||||||
|
income: MoneyCategoryItem[]
|
||||||
|
setCategories: (categories: MoneyCategoryItem[]) => void
|
||||||
|
addCategory: (category: MoneyCategoryItem) => void
|
||||||
|
removeCategory: (id: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMoneyCategoryStore = create<MoneyCategoryStore>()(set => ({
|
||||||
|
categories: [],
|
||||||
|
expense: [],
|
||||||
|
income: [],
|
||||||
|
|
||||||
|
setCategories: categories =>
|
||||||
|
set({
|
||||||
|
categories,
|
||||||
|
expense: categories.filter(c => c.type === 0),
|
||||||
|
income: categories.filter(c => c.type === 1),
|
||||||
|
}),
|
||||||
|
|
||||||
|
addCategory: category =>
|
||||||
|
set((state) => {
|
||||||
|
const newCategories = [...state.categories, category]
|
||||||
|
return {
|
||||||
|
categories: newCategories,
|
||||||
|
expense: newCategories.filter(c => c.type === 0),
|
||||||
|
income: newCategories.filter(c => c.type === 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
removeCategory: id =>
|
||||||
|
set((state) => {
|
||||||
|
const newCategories = state.categories.filter(c => c.id !== id)
|
||||||
|
return {
|
||||||
|
categories: newCategories,
|
||||||
|
expense: newCategories.filter(c => c.type === 0),
|
||||||
|
income: newCategories.filter(c => c.type === 1),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}))
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
import { MoneyCategoryItem } from '@/types/money/category'
|
||||||
|
import { MoneyRecordGroup, MoneyRecordItem } from '@/types/money/record'
|
||||||
@@ -11,8 +11,3 @@ export interface MoneyCategoryBase {
|
|||||||
export interface MoneyCategoryItem extends MoneyCategoryBase {
|
export interface MoneyCategoryItem extends MoneyCategoryBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryMoneyCategoryResult {
|
|
||||||
income: MoneyCategoryItem[]
|
|
||||||
expense: MoneyCategoryItem[]
|
|
||||||
}
|
|
||||||
@@ -189,22 +189,7 @@ func getUserCategory(c *router.Context) error {
|
|||||||
return c.AddError(err)
|
return c.AddError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 type 分组并保持顺序
|
return response.Success(c).Data(userCategories).Send()
|
||||||
result := map[string][]mapx.M{
|
|
||||||
"income": {},
|
|
||||||
"expense": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cat := range userCategories {
|
|
||||||
t := cat.GetInt("type")
|
|
||||||
if t == 1 {
|
|
||||||
result["income"] = append(result["income"], cat)
|
|
||||||
} else if t == 0 {
|
|
||||||
result["expense"] = append(result["expense"], cat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Success(c).Data(result).Send()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertMoneyRecord(c *router.Context) error {
|
func insertMoneyRecord(c *router.Context) error {
|
||||||
|
|||||||
Reference in new issue
Block a user