u
This commit is contained in:
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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user