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