This commit is contained in:
oneao committed 2026-01-26 22:31:12 +08:00
1 parent 5a79414319
commit 8663cb2e3d
28 files changed
+744 -756

No files matched your search

@@ -1,43 +1,40 @@
import type { icons } from 'lucide-react-native'
import type { SortableGridRenderItem } from 'react-native-sortables'
import type { NavItem } from '@/store/navigation'
import { Slot, usePathname, useRouter } from 'expo-router'
import { useCallback, useEffect, useState } from 'react'
import { Pressable, Text, TouchableOpacity, Vibration, View } from 'react-native'
import { Text, TouchableOpacity, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables'
import { ADrawer, LucideIcon } from '@/components/common/ao'
interface NavItem {
label: string
path: string
icon: keyof typeof icons
}
const NAV_MENUS: NavItem[] = [
{ label: '记账', path: '/money', icon: 'CircleDollarSign' },
{ label: '恋爱记', path: '/love', icon: 'Heart' },
{ label: '聊天', path: '/chat', icon: 'MessageCircle' },
{ label: '个人中心', path: '/user', icon: 'CircleUserRound' },
]
import { useNavStore } from '@/store/navigation'
export default function TabsLayout() {
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
const router = useRouter()
const pathname = usePathname()
const navStore = useNavStore()
const [showNavDrawer, setShowNavDrawer] = useState(false)
const [draggingId, setDraggingId] = useState<string>('')
const [currentNav, setCurrentNav] = useState<NavItem>({
label: '',
path: '',
icon: 'AArrowDown',
label: 'Welcome',
path: '/',
icon: 'Sparkles',
})
useEffect(() => {
const current = NAV_MENUS.find(item => item.path === pathname)
const current = navStore.navMenus.find(item => item.path === pathname)
if (current) {
setCurrentNav(current)
}
else {
setCurrentNav({
label: 'Welcome',
path: '/',
icon: 'Sparkles',
})
}
}, [pathname])
const refreshPage = () => {
@@ -46,15 +43,14 @@ export default function TabsLayout() {
const renderNavItem = useCallback<SortableGridRenderItem<NavItem>>(
({ item }) => (
<Pressable
<TouchableOpacity
className="
flex
flex-col
items-center
justify-center
bg-[white]
bg-[#f9f9f9]
rounded-md
shadow-md
p-3
"
onPress={() => {
@@ -66,7 +62,7 @@ export default function TabsLayout() {
<Text className="text-sm mt-2">
{item.label}
</Text>
</Pressable>
</TouchableOpacity>
),
[],
)
@@ -86,6 +82,7 @@ export default function TabsLayout() {
px-2
gap-4
"
style={{ zIndex: 0 }}
>
<View className="bg-white rounded-md shadow-lg h-12 w-12">
<TouchableOpacity className="w-full h-full items-center justify-center">
@@ -128,11 +125,15 @@ export default function TabsLayout() {
columnGap={16}
rowGap={16}
columns={3}
data={NAV_MENUS}
data={navStore.navMenus}
keyExtractor={item => item.path}
renderItem={renderNavItem}
scrollableRef={scrollableRef}
autoScrollActivationOffset={200}
showDropIndicator
onDragEnd={(dragData) => {
navStore.setNavMenus(dragData.data)
}}
/>
</Animated.ScrollView>
</ADrawer>
Whitespace-only changes.
+23 -9
View File
@@ -1,20 +1,34 @@
import { useRouter } from 'expo-router'
import { Text, View } from 'react-native'
import { Image, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { AButton } from '@/components/common/ao'
// 这个页面的作用是作为占位页,避免进入 (tabs) layout 时出现 404 页面。
// 如果想指定默认页面,可以通过 router.replace 或 TabTrigger 指向具体子页面。
export default function Index() {
const router = useRouter()
return (
<SafeAreaView>
<View>
<AButton onPress={() => {
router.push('/money')
}}
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 items-center justify-center px-6">
<Image
source={require('@/static/images/welcome.png')}
className="w-86 h-48 mb-6"
resizeMode="contain"
/>
<Text className="text-2xl font-semibold text-gray-900 mb-3">
🎉 欢迎使用 OneAO
</Text>
<Text className="text-base text-gray-500 text-center mb-10">
记录生活,记录情绪,也记录每一笔重要的支出
</Text>
<AButton
className="w-full"
onPress={() => router.replace('/money')}
>
测试
开始使用
</AButton>
</View>
</SafeAreaView>
@@ -1,20 +1,17 @@
import { useFocusEffect, useRouter } from 'expo-router'
import React, { useCallback, useEffect, useState } from 'react'
import { Image, Text, TouchableOpacity, View } from 'react-native'
import { Drawer } from 'react-native-drawer-layout'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { ADatePicker, AListItem, LucideIcon } from '@/components/common/ao'
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 { useAppStore } from '@/store/app'
import { useMoneyRecordStore } from '@/store/moneyRecord'
import { getCurrentYearMonth } from '@/utils/time'
import { toast } from '@/utils/toast'
import TransactionsList from './components/TransactionsList'
export default function Money() {
const appStore = useAppStore()
const moneyRecordStore = useMoneyRecordStore()
const router = useRouter()
// 安全高度
@@ -47,12 +44,6 @@ export default function Money() {
})
}
useEffect(() => {
if (!appStore.ready)
return
getMoneyRecords()
}, [appStore.ready, searchParams])
useFocusEffect(
useCallback(() => {
// 页面显示 / 返回时
@@ -68,120 +59,78 @@ export default function Money() {
<View
className="flex-1"
>
<Drawer
open={showActionsMenu}
onOpen={() => setShowActionsMenu(true)}
onClose={() => setShowActionsMenu(false)}
drawerPosition="right"
drawerStyle={{
width: '70%',
}}
renderDrawerContent={() => {
return (
<SafeAreaView className="flex-col px-3">
<Image
source={require('@/static/images/logo.png')}
style={{ width: 40, height: 40 }}
className="self-center"
/>
<View className="mt-2">
<Text className="text-gray-600 font-semibold text-sm">管理功能</Text>
<View className="p-1">
<AListItem
icon="LayoutGrid"
text="类别设置"
onPress={() => {
router.navigate('/money/category/setting')
}}
/>
</View>
</View>
<View className="mt-2">
<Text className="text-gray-600 font-semibold text-sm">统计分析</Text>
<View className="p-1">
<AListItem icon="ChartSpline" text="图表分析" />
</View>
</View>
</SafeAreaView>
)
<View
style={{
paddingTop: insets.top,
backgroundColor: '#fff',
elevation: 3,
}}
>
<View
style={{
paddingTop: insets.top,
backgroundColor: '#fff',
elevation: 3,
}}
>
<View className="flex flex-row justify-between items-center px-3">
<TouchableOpacity
onPress={() => setShowDatePicker(true)}
className="flex flex-row items-center gap-1"
>
<Text className="font-semibold text-lg">{searchParams.record_time}</Text>
<LucideIcon icon="ChevronDown" size={16} color="#666" clickable={false} />
</TouchableOpacity>
<TouchableOpacity
className="flex flex-row items-center gap-2"
onPress={() => {
setShowActionsMenu(true)
}}
>
<LucideIcon icon="Menu" size={24} clickable={false} />
</TouchableOpacity>
</View>
<View
className="px-3 py-3"
<View className="flex flex-row justify-between items-center px-3">
<TouchableOpacity
onPress={() => setShowDatePicker(true)}
className="flex flex-row items-center gap-1"
>
<View
className="flex flex-row gap-4"
style={{
borderBottomLeftRadius: 16,
borderBottomRightRadius: 18,
}}
>
<View className="flex-1">
<Text className="text-gray-500 text-sm">收入</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalIncome()}
</Text>
</View>
<Text className="font-semibold text-lg">{searchParams.record_time}</Text>
<LucideIcon icon="ChevronDown" size={16} color="#666" clickable={false} />
</TouchableOpacity>
<View className="flex-1">
<Text className="text-gray-500 text-sm">支出</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalExpense()}
</Text>
</View>
<TouchableOpacity
className="flex flex-row items-center gap-2"
onPress={() => {
setShowActionsMenu(true)
}}
>
<LucideIcon icon="Menu" size={24} clickable={false} />
</TouchableOpacity>
<View className="flex-1">
<Text className="text-gray-500 text-sm">结余</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalBalance()}
</Text>
</View>
</View>
<View
className="px-3 py-3"
>
<View
className="flex flex-row gap-4"
style={{
borderBottomLeftRadius: 16,
borderBottomRightRadius: 18,
}}
>
<View className="flex-1">
<Text className="text-gray-500 text-sm">收入</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalIncome()}
</Text>
</View>
{/* <View
<View className="flex-1">
<Text className="text-gray-500 text-sm">支出</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalExpense()}
</Text>
</View>
<View className="flex-1">
<Text className="text-gray-500 text-sm">结余</Text>
<Text
className="text-lg font-semibold mt-0.5"
numberOfLines={1}
ellipsizeMode="tail"
>
{moneyRecordStore.getTotalBalance()}
</Text>
</View>
</View>
{/* <View
className="flex-row items-center gap-3 mt-2"
style={{
borderBottomLeftRadius: 8,
@@ -196,58 +145,98 @@ export default function Money() {
</View>
<Text className="text-xs text-blue-500">点击设置</Text>
</View> */}
</View>
</View>
</View>
<View className="flex-1 mt-2">
<TransactionsList
records={moneyRecordStore.records}
onUpdate={(data) => {
router.navigate({
pathname: '/money/record/editor',
params: { id: data.id },
})
}}
onDetail={(id) => {
router.navigate({
pathname: '/money/record/detail',
params: { id },
})
}}
onDelete={(id) => {
moneyRecordStore.deleteMoneyRecord(id)
}}
onRefreshData={() => {
getMoneyRecords()
}}
/>
</View>
<Fab
size="lg"
onPress={() => {
router.navigate('/money/record/editor')
}}
>
<FabIcon as={AddIcon} size="xl" />
</Fab>
<ADatePicker
show={showDatePicker}
mode="month"
date={searchParams.record_time}
onConfirm={(val) => {
setSearchParams({
...searchParams,
record_time: val,
<View className="flex-1 mt-2">
<TransactionsList
records={moneyRecordStore.records}
onUpdate={(data) => {
router.navigate({
pathname: '/money/record/editor',
params: { id: data.id },
})
setShowDatePicker(false)
}}
onClose={() => {
setShowDatePicker(false)
onDetail={(id) => {
router.navigate({
pathname: '/money/record/detail',
params: { id },
})
}}
onDelete={(id) => {
moneyRecordStore.deleteMoneyRecord(id)
}}
onRefreshData={() => {
getMoneyRecords()
}}
/>
</Drawer>
</View>
<Fab
size="lg"
onPress={() => {
router.navigate('/money/record/editor')
}}
className="mb-20"
>
<FabIcon as={AddIcon} size="xl" />
</Fab>
<ADatePicker
show={showDatePicker}
mode="month"
date={searchParams.record_time}
onConfirm={(val) => {
setSearchParams({
...searchParams,
record_time: val,
})
setShowDatePicker(false)
}}
onClose={() => {
setShowDatePicker(false)
}}
/>
<ADrawer
visible={showActionsMenu}
hideModal={() => {
setShowActionsMenu(false)
}}
menuPosition="right"
height="60%"
showHeader={false}
>
<SafeAreaView className="flex-col px-3">
<Image
source={require('@/static/images/logo.png')}
style={{ width: 40, height: 40 }}
className="self-center"
/>
<View className="mt-2">
<Text className="text-gray-600 font-semibold text-sm">管理功能</Text>
<View className="p-1">
<AListItem
icon="LayoutGrid"
text="类别设置"
onPress={() => {
router.navigate('/money/category/setting')
}}
/>
</View>
</View>
<View className="mt-2">
<Text className="text-gray-600 font-semibold text-sm">统计分析</Text>
<View className="p-1">
<AListItem icon="ChartSpline" text="图表分析" />
</View>
</View>
</SafeAreaView>
</ADrawer>
</View>
)
}