This commit is contained in:
oneao committed 2025-11-11 16:35:32 +08:00
1 parent 098a061308
commit 994321d049
40 files changed
+1401 -359

No files matched your search

@@ -0,0 +1,93 @@
import type { MoneyCategory } from '@/types/money'
import { useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import SegmentedTabs from '@/components/common/ao/SegmentedTabs'
import {
Drawer,
DrawerBackdrop,
DrawerBody,
DrawerContent,
DrawerHeader,
} from '@/components/ui/drawer'
import { Grid, GridItem } from '@/components/ui/grid'
import MoneyCategoryIcon from './MoneyCategoryIcon'
interface AddMoneyDrawerProps {
isOpen: boolean
onClose: () => void
}
export default function AddMoneyDrawer({
isOpen,
onClose,
}: AddMoneyDrawerProps) {
const insets = useSafeAreaInsets()
const [seletedIndex, setSelectedIndex] = useState(0)
const categories: MoneyCategory[] = []
return (
<Drawer
isOpen={isOpen}
size="full"
anchor="bottom"
onClose={onClose}
>
<DrawerBackdrop />
<DrawerContent
style={{
padding: 0,
paddingLeft: 16,
paddingRight: 16,
}}
>
<DrawerHeader
className="flex items-center justify-between"
style={{
paddingTop: insets.top,
margin: 0,
padding: 0,
}}
>
<View className="w-1" />
<SegmentedTabs
value={seletedIndex}
tabs={[
{ label: '收入' },
{ label: '支出' },
]}
onChange={(index) => {
setSelectedIndex(index)
}}
style={{ width: 180 }}
className="mx-auto"
/>
<Pressable onPress={onClose}>
<Text className="text-gray-500 font-medium">取消</Text>
</Pressable>
</DrawerHeader>
<DrawerBody>
<Grid className="gap-4" _extra={{ className: 'grid-cols-4' }}>
{categories.map((item, index) => (
<GridItem
key={index}
className="flex flex-col items-center justify-center"
_extra={{ className: 'col-span-1' }}
>
<Pressable>
<View className="w-14 h-14 rounded-full bg-gray-200 flex items-center justify-center">
<MoneyCategoryIcon name={item.name} size={28} />
</View>
<Text className="text-sm mt-1">{item.label}</Text>
</Pressable>
</GridItem>
))}
</Grid>
</DrawerBody>
</DrawerContent>
</Drawer>
)
}
@@ -0,0 +1,40 @@
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,
shouru: IconShouru,
shucai: IconShucai,
yule: IconYule,
zhichu: IconZhichu,
}
const DefaultIcon = IconLingshi
interface CategoryIconProps {
name?: string
size?: number
color?: string
}
/**
* 分类图标组件
*/
export default function MoneyCategoryIcon({
name,
size = 24,
color,
}: CategoryIconProps) {
const IconComponent = iconMap[name ?? ''] || DefaultIcon
return <IconComponent size={size} color={color} />
}
@@ -1,20 +0,0 @@
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, color?: string) {
const IconComponent = iconMap[name] || DefaultIcon
return <IconComponent size={size} color={color} />
}
@@ -1,6 +1,5 @@
import React from 'react'
import { Text, View } from 'react-native'
import { LucideIcon } from '@/components/common/ao'
import IconFont from '@/components/iconfont'
interface Transaction {
@@ -35,16 +34,16 @@ const sections: DaySection[] = [
totalIncome: 1200,
totalExpense: 560,
data: [
{ id: '4', title: '早餐', description: '面包牛奶', amount: 15, type: 'expense' },
{ 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">
<View className="mt-2 space-y-6">
{sections.map(section => (
<View key={section.title} className="space-y-4">
<View className="py-2">
@@ -70,25 +69,19 @@ export default function StaticTransactions() {
{section.data.map(item => (
<View
key={item.id}
className="flex-row items-center p-2"
className="flex-row items-center p-2 gap-4"
>
{/* 图标 */}
<View className="w-12 h-12 rounded-full bg-white shadow-md items-center justify-center mr-4">
{/* <IconFont name="shucai" size={28} /> */}
<View className="w-12 h-12 rounded-full bg-white shadow-md items-center justify-center">
<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'
}`}
className="text-sm text-gray-500"
>
{item.type === 'income' ? '+' : '-'}
¥
@@ -1,22 +1,33 @@
import type { LayoutChangeEvent } from 'react-native'
import { LinearGradient } from 'expo-linear-gradient'
import React, { useRef, useState } from 'react'
import { Text, View } from 'react-native'
import { ScrollView, Text, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { LucideIcon } from '@/components/common/ao'
import { IconJiaotong } from '@/components/iconfont'
import { Fab, FabIcon } from '@/components/ui/fab'
import { Grid, GridItem } from '@/components/ui/grid'
import { AddIcon } from '@/components/ui/icon'
import { Progress, ProgressFilledTrack } from '@/components/ui/progress'
import AddMoneyDrawer from './components/AddDrawer'
import TransactionsList from './components/TransactionsList'
export default function Money() {
const [showDrawer, setShowDrawer] = useState(false)
const insets = useSafeAreaInsets()
const [gradientHeight, setGradientHeight] = useState(200) // 默认渐变高度
const gridLayout = useRef({ y: 0, height: 0 })
const [gradientHeight, setGradientHeight] = useState(200)
const gridLayout = useRef<{ y: number, height: number } | null>(null)
const onGridLayout = (e: LayoutChangeEvent) => {
const { y, height } = e.nativeEvent.layout
gridLayout.current = { y, height }
setGradientHeight(y + height / 2 + 16 + insets.top)
// 仅首次布局计算高度
if (!gridLayout.current) {
const { y, height } = e.nativeEvent.layout
gridLayout.current = { y, height }
setGradientHeight(y + height / 2 + 16 + insets.top)
}
}
return (
<View
className="flex-1 relative bg-white"
@@ -25,9 +36,8 @@ export default function Money() {
}}
>
<LinearGradient
colors={['#a3f7bf', '#d4f9e2', '#ffffff']}
locations={[0, 0.85, 1]}
colors={['#fef6e4', '#fff8e1', '#ffffff']}
locations={[0, 0.95, 1]}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={{
@@ -40,8 +50,7 @@ export default function Money() {
}}
/>
{/* 页面内容 */}
<View style={{ zIndex: 10, padding: 8 }}>
<View style={{ zIndex: 10, paddingLeft: 12, paddingRight: 12 }}>
<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-base">2025/11/10</Text>
@@ -52,51 +61,52 @@ export default function Money() {
</View>
</View>
<View>
<View className="flex flex-col items-center">
<Text className="text-gray-600 text-xs">
<Text>余额</Text>
<Text className="text-green-500">↑20%</Text>
</Text>
<Text className="font-bold text-xl">
22313.00
<View className="mt-2">
{/* 余额区域 */}
<View className="items-center mb-3">
<Text className="text-gray-500 text-xs">
余额
<Text className="text-red-500">↓20%</Text>
</Text>
<Text className="font-bold text-2xl mt-0.5">22,313.00</Text>
</View>
<View className="flex flex-row justify-between mt-2">
<View className="flex flex-col items-center">
<Text className="text-gray-600 text-xs">
<Text>收入</Text>
{/* 收入/支出/结余 */}
<View className="flex-row justify-around py-2">
<View className="items-center">
<Text className="text-gray-500 text-xs">
收入
<Text className="text-green-500">↑20%</Text>
</Text>
<Text className="text-lg font-bold">11.0</Text>
<Text className="text-lg font-semibold mt-0.5">11.0</Text>
</View>
<View className="flex flex-col items-center">
<Text className="text-gray-600 text-xs">
<Text>支出</Text>
<View className="items-center">
<Text className="text-gray-500 text-xs">
支出
<Text className="text-green-500">↑20%</Text>
</Text>
<Text className="text-lg font-bold">11.0</Text>
<Text className="text-lg font-semibold mt-0.5">11.0</Text>
</View>
<View className="flex flex-col items-center">
<Text className="text-gray-600 text-xs">
<Text>结余</Text>
<View className="items-center">
<Text className="text-gray-500 text-xs">
结余
<Text className="text-green-500">↑20%</Text>
</Text>
<Text className="text-lg font-bold">11.0</Text>
<Text className="text-lg font-semibold mt-0.5">11.0</Text>
</View>
</View>
<View className="flex flex-row items-center gap-4 mt-4 ">
<Text className="text-xs text-gray-600">预算</Text>
{/* 预算进度 */}
<View className="flex-row items-center gap-3 mt-3">
<Text className="text-xs text-gray-600 w-8 text-right">预算</Text>
<View className="flex-1">
<Progress>
<Progress size="sm">
<ProgressFilledTrack />
</Progress>
</View>
<Text className="text-xs text-gray-600">点击设置</Text>
<Text className="text-xs text-blue-500">点击设置</Text>
</View>
</View>
@@ -121,6 +131,30 @@ export default function Money() {
</View>
</View>
<View style={{ flex: 1, paddingHorizontal: 12 }}>
<ScrollView
contentContainerStyle={{ paddingBottom: 16 }}
showsVerticalScrollIndicator={false}
>
<TransactionsList />
</ScrollView>
</View>
<Fab onPress={() => {
setShowDrawer(true)
}}
>
<FabIcon as={AddIcon} />
</Fab>
<AddMoneyDrawer
isOpen={showDrawer}
onClose={() => {
setShowDrawer(false)
}}
/>
</View>
)
}
@@ -1,35 +1,3 @@
// import { LinearGradient } from 'expo-linear-gradient'
// import React from 'react'
// import { Dimensions, View } from 'react-native'
// import { useSafeAreaInsets } from 'react-native-safe-area-context'
// export default function Money() {
// const insets = useSafeAreaInsets()
// const { height } = Dimensions.get('window')
// const gradientHeight = height / 3 // 渐变高度 1/3
// return (
// <View className="flex-1 relative">
// {/* 渐变层 */}
// <LinearGradient
// colors={['#e6f0ff', '#ffffff']} // 顶部蓝色 → 白色
// start={{ x: 0, y: 0 }}
// end={{ x: 0, y: 1 }}
// style={{
// position: 'absolute',
// top: 0,
// left: 0,
// right: 0,
// height: gradientHeight,
// paddingTop: insets.top,
// paddingHorizontal: 8,
// }}
// >
// {/* 可以在这里放置顶部卡片或统计信息 */}
// </LinearGradient>
// </View>
// )
// }
import { LinearGradient } from 'expo-linear-gradient'
import React, { useState } from 'react'
import { ScrollView, Text, View } from 'react-native'