This commit is contained in:
oneao committed 2025-11-10 17:31:45 +08:00
1 parent 7ed98a04c1
commit dee2eab404
25 files changed
+1037 -518

No files matched your search

@@ -3,7 +3,7 @@ import CustomTabbar from '@/components/custom/Tabbar'
import { toast } from '@/utils/toast'
export const unstable_settings = {
initialRouteName: 'money',
initialRouteName: 'money/index',
}
export default function TabsLayout() {
@@ -1,159 +0,0 @@
import type { SegmentedTabItem } from '@/components/common/ao/SegmentedTabs'
import React, { useEffect, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
import { SafeAreaView } from 'react-native-safe-area-context'
import { LucideIcon } from '@/components/common/ao'
import SegmentedTabs from '@/components/common/ao/SegmentedTabs'
import { Button, ButtonText } from '@/components/ui/button'
import {
Drawer,
DrawerBackdrop,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
} from '@/components/ui/drawer'
import { CloseIcon, Icon } from '@/components/ui/icon'
function Detail() {
const [dateSelected, setDateSelected] = useState(false)
const [filterOpen, setFilterOpen] = useState(false)
useEffect(() => {
console.log('Detail加载了')
}, [])
// 动画角度
const dateRotation = useSharedValue(0)
const filterRotation = useSharedValue(0)
const toggleDate = () => {
setDateSelected(prev => !prev)
dateRotation.value = withTiming(dateRotation.value === 0 ? 180 : 0, { duration: 200 })
}
const toggleFilter = () => {
setFilterOpen(prev => !prev)
filterRotation.value = withTiming(filterRotation.value === 0 ? 180 : 0, { duration: 200 })
}
const dateAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${dateRotation.value}deg` }],
}))
const filterAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${filterRotation.value}deg` }],
}))
return (
<View>
<View className="flex-row justify-between">
<Pressable
className="flex-row items-center gap-1"
onPress={toggleDate}
>
<Text style={{ fontSize: 16 }}>今天</Text>
<Animated.View style={dateAnimatedStyle}>
<LucideIcon icon="ChevronDown" size={18} />
</Animated.View>
</Pressable>
<Pressable
className="flex-row items-center gap-1"
onPress={toggleFilter}
>
<Text style={{ fontSize: 16 }}>筛选</Text>
<Animated.View style={filterAnimatedStyle}>
<LucideIcon icon="ChevronDown" size={18} />
</Animated.View>
</Pressable>
</View>
<Drawer
isOpen={dateSelected}
anchor="bottom"
onClose={() => {
setDateSelected(false)
}}
size="md"
>
<DrawerBackdrop />
<DrawerContent style={{ padding: 12 }}>
<DrawerBody style={{ padding: 0, marginTop: 0, marginBottom: 0 }}>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
<Text>This is the basic drawer component.</Text>
</DrawerBody>
</DrawerContent>
</Drawer>
</View>
)
}
function Chart() {
useEffect(() => {
console.log('Chart记载了')
}, [])
return (
<View>
<Text>这里是视图内这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容这里是视图内容容</Text>
</View>
)
}
export default function MoneyTest() {
const [selectedIndex, setSelectedIndex] = useState(0)
const tabs: SegmentedTabItem[] = [
{
label: '明细',
icon: 'ScrollText',
content: <Detail />, // ✅ 注意这里不要调用 Detail()
},
{
label: '视图',
icon: 'ChartPie',
content: <Chart />, // ✅ 注意这里不要调用 Chart()
},
{
label: '视图2',
icon: 'ChartPie',
content: (
<View>
<Text>这里是视图内容2</Text>
</View>
),
},
]
return (
<SafeAreaView style={{ flex: 1, paddingTop: 4, paddingLeft: 8, paddingRight: 8 }}>
<SegmentedTabs
headerWidth={220}
tabs={tabs}
value={selectedIndex}
onChange={setSelectedIndex}
cacheContent={true}
/>
</SafeAreaView>
)
}
@@ -0,0 +1,53 @@
import React from 'react'
import { Text, View } from 'react-native'
function BalanceCard() {
return (
<View className="mt-6 rounded-lg overflow-hidden shadow-lg shadow-gray-300 bg-white">
<View className="p-4 relative">
<View className="absolute top-2 left-2 bg-green-50 rounded-md px-3 py-1.5 flex-row items-center z-10 border border-green-100">
<View className="w-2 h-2 bg-green-500 rounded-full mr-2" />
<Text className="text-green-700 text-xs font-semibold">账单已出</Text>
</View>
{/* 总余额 */}
<View className="items-center mb-4 mt-3">
<Text className="text-gray-500 text-sm font-medium mb-2">总余额 (CNY)</Text>
<Text className="text-4xl font-black text-gray-800 text-center tracking-tight">
¥ 12,322.67
</Text>
</View>
{/* 收入 / 支出 */}
<View className="flex-row justify-between rounded-lg pt-4">
{/* 收入 */}
<View className="flex-1 items-center pr-3">
<View className="flex-row justify-center w-full mb-2">
<Text className="text-gray-600 text-sm font-medium">收入</Text>
<Text className="text-green-500 text-xs font-semibold ml-1">↑ +10%</Text>
</View>
<Text className="text-2xl font-bold text-gray-800 text-center">
¥ 12,232.00
</Text>
</View>
{/* 分隔线 */}
<View className="w-px bg-gray-200 mx-1" />
{/* 支出 */}
<View className="flex-1 items-center pl-3">
<View className="flex-row justify-center w-full mb-2">
<Text className="text-gray-600 text-sm font-medium">支出</Text>
<Text className="text-red-500 text-xs font-semibold ml-1">↓ -20%</Text>
</View>
<Text className="text-2xl font-bold text-gray-800 text-center">
¥ 1,980.00
</Text>
</View>
</View>
</View>
</View>
)
}
export default BalanceCard
@@ -0,0 +1,20 @@
import React from 'react'
import { IconJiaotong, IconLingshi, IconRiyongpin, IconShouru, IconShucai, IconYule, IconZhichu } from '@/components/iconfont'
// 图标映射表
const iconMap: Record<string, React.ElementType> = {
jiaotong: IconJiaotong,
lingshi: IconLingshi,
riyongpin: IconRiyongpin,
shucai: IconShucai,
yule: IconYule,
}
// 默认图标
const DefaultIcon = IconLingshi
// 根据类别 name 返回图标组件
export function getCategoryIcon(name: string, size = 24,type: , color?: string) {
const IconComponent = iconMap[name] || DefaultIcon
return <IconComponent size={size} color={color} />
}
@@ -0,0 +1,56 @@
import { Pressable, Text, View } from 'react-native'
import Animated, { useAnimatedStyle, withSpring, withTiming } from 'react-native-reanimated'
import { IconQuxian1 } from '@/components/iconfont'
interface TabSelectorProps {
options: string[]
selected: number
onSelect: (tab: number) => void
}
const TabSelector: React.FC<TabSelectorProps> = ({ options, selected, onSelect }) => {
return (
<View className="flex-row gap-4 items-center h-10 mt-2">
{options.map((tab, index) => {
const isActive = selected === index
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ rotate: '50deg' },
{ scale: withSpring(isActive ? 1 : 0.8, { damping: 12, stiffness: 120 }) },
],
opacity: withTiming(isActive ? 1 : 0, { duration: 200 }),
}))
return (
<View key={tab} className="relative h-full items-center">
<Pressable
onPress={() => onSelect(index)}
className="h-full items-center justify-center"
>
<Text
className={`${isActive ? 'font-bold text-xl text-black' : 'font-normal text-base text-gray-600 '}`}
>
{tab}
</Text>
<Animated.View
style={[
{
position: 'absolute',
bottom: -2,
},
animatedStyle,
]}
>
<IconQuxian1 size={22} color="#7566f3" />
</Animated.View>
</Pressable>
</View>
)
})}
</View>
)
}
export default TabSelector
@@ -0,0 +1,103 @@
import React from 'react'
import { Text, View } from 'react-native'
import { LucideIcon } from '@/components/common/ao'
import IconFont from '@/components/iconfont'
interface Transaction {
id: string
description: string
title: string
amount: number
type: 'income' | 'expense'
}
interface DaySection {
title: string
totalIncome: number
totalExpense: number
data: Transaction[]
}
// 模拟数据
const sections: DaySection[] = [
{
title: '2024/11/10',
totalIncome: 3200,
totalExpense: 1980,
data: [
{ id: '1', title: '午餐', description: '公司食堂', amount: 25, type: 'expense' },
{ id: '2', title: '打车', description: '回家', amount: 40, type: 'expense' },
{ id: '3', title: '工资', description: '11月工资', amount: 3200, type: 'income' },
],
},
{
title: '2024/11/09',
totalIncome: 1200,
totalExpense: 560,
data: [
{ id: '4', title: '早餐', description: '面包牛奶', amount: 15, type: 'expense' },
{ id: '5', title: '电影票', description: '周末电影', amount: 45, type: 'expense' },
{ id: '6', title: '兼职收入', description: '家教', amount: 1200, type: 'income' },
],
},
]
export default function StaticTransactions() {
return (
<View className="mt-4 space-y-6 px-2">
{sections.map(section => (
<View key={section.title} className="space-y-4">
<View className="py-2">
<View className="flex-row items-center justify-between">
<View className="flex-row items-center gap-2">
<Text className="text-gray-700 font-semibold text-base">{section.title}</Text>
<Text className="text-gray-400 text-sm">星期日</Text>
</View>
<View className="flex-row items-center gap-4">
<Text className="text-gray-500 text-sm">
收入 ¥
{section.totalIncome}
</Text>
<Text className="text-gray-500 text-sm">
支出 ¥
{section.totalExpense}
</Text>
</View>
</View>
</View>
{/* 交易列表 */}
{section.data.map(item => (
<View
key={item.id}
className="flex-row items-center bg-white rounded-md p-4 shadow-md mb-4"
>
{/* 图标 */}
<View className="w-12 h-12 rounded-full bg-white shadow-md items-center justify-center mr-4">
{/* <IconFont name="shucai" size={28} /> */}
<IconFont name='jiaotong' size={28} />
</View>
{/* 标题 + 描述 */}
<View className="flex-1">
<Text className="text-gray-800 font-medium text-base">{item.title}</Text>
<Text className="text-gray-400 text-sm mt-0.5">{item.description}</Text>
</View>
{/* 金额 */}
<Text
className={`font-semibold text-base ${
item.type === 'income' ? 'text-green-600' : 'text-red-600'
}`}
>
{item.type === 'income' ? '+' : '-'}
¥
{item.amount}
</Text>
</View>
))}
</View>
))}
</View>
)
}
@@ -0,0 +1,59 @@
import { LinearGradient } from 'expo-linear-gradient'
import React, { useState } from 'react'
import { ScrollView, Text, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { LucideIcon } from '@/components/common/ao'
import BalanceCard from './components/BalanceCard'
import TabSelector from './components/TabSelector'
import TransactionsList from './components/TransactionsList'
export default function Money() {
const insets = useSafeAreaInsets()
const [selected, setSelected] = useState(0)
return (
<LinearGradient
colors={['#f2f6ff', '#ffffff']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
className="flex-1"
style={{
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: 8,
paddingRight: 8,
}}
>
{/* 顶部固定栏 */}
<View style={{ zIndex: 10 }}>
<View className="flex flex-row justify-between items-center py-2">
<View className="flex flex-row items-center gap-2">
<Text className="font-bold text-lg">2025/11/10</Text>
<LucideIcon icon="CircleChevronDown" size={16} />
</View>
<View className="flex flex-row items-center gap-2">
<LucideIcon icon="SlidersHorizontal" />
<Text>筛选</Text>
</View>
</View>
<TabSelector
options={['明细', '视图']}
selected={selected}
onSelect={tab => setSelected(tab)}
/>
</View>
{/* 可滚动内容 */}
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: 20 }}
style={{ marginTop: 0 }}
>
<BalanceCard />
<TransactionsList />
</ScrollView>
</LinearGradient>
)
}