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'
@@ -58,6 +58,7 @@ export default function SegmentedTabs({
}: SegmentedTabsProps) {
const [selectedIndex, setSelectedIndex] = useState(value ?? defaultIndex)
const [headerMeasuredWidth, setHeaderMeasuredWidth] = useState(0)
const [headerMeasuredHeight, setHeaderMeasuredHeight] = useState(tabHeight)
const [ready, setReady] = useState(false)
const sliderX = useSharedValue(0)
@@ -65,8 +66,10 @@ export default function SegmentedTabs({
const actualHeaderWidth = width ?? headerMeasuredWidth
const tabWidth = actualHeaderWidth ? actualHeaderWidth / tabs.length : 0
// 🔹 新逻辑:根据外层实际高度动态决定 slider 高度
const sliderHeight = Math.max(headerMeasuredHeight - spacing * 2, 0)
const sliderWidth = tabWidth - spacing * 2
const sliderHeight = tabHeight - spacing * 2
useEffect(() => {
if (value !== undefined)
@@ -98,8 +101,9 @@ export default function SegmentedTabs({
}
const handleHeaderLayout = (event: any) => {
const { width } = event.nativeEvent.layout
const { width, height } = event.nativeEvent.layout
setHeaderMeasuredWidth(width)
setHeaderMeasuredHeight(height || tabHeight)
if (width > 0 && !ready)
setReady(true)
}
@@ -0,0 +1,64 @@
/* eslint-disable */
/* tslint:disable */
import React from 'react';
import type { FunctionComponent } from 'react';
import type { ViewProps } from 'react-native';
import { Svg, GProps, Path } from 'react-native-svg';
import { getIconColor } from './helper';
interface Props extends Omit<GProps, 'color'>, ViewProps {
size?: number;
color?: string | string[];
}
const IconIcon: FunctionComponent<Props> = ({
size = 18,
color,
...rest
}) => {
return (
<Svg viewBox="0 0 1024 1024" width={size} height={size} {...rest}>
<Path
d="M834.80808 440.235536c-2.707668-17.020652-18.621103-28.635177-35.722596-25.941835-1.290389 0.203638-130.923842 20.465101-286.911522 20.465101-155.991774 0-285.626249-20.261463-286.916639-20.465101-17.050328-2.712785-33.019021 8.922207-35.722596 25.941835-2.697435 17.020652 8.906857 33.013905 25.931602 35.71748 5.456269 0.86367 135.542023 21.237697 296.706609 21.237697 161.15947 0 291.245224-20.374026 296.701492-21.237697C825.902246 473.250464 837.506539 457.256188 834.80808 440.235536z"
fill={getIconColor(color, 0, '#999999')}
/>
<Path
d="M397.310911 539.004027c-17.238616 0-31.215956 13.972223-31.215956 31.215956l0 28.716019c0 17.243733 13.977339 31.215956 31.215956 31.215956s31.215956-13.972223 31.215956-31.215956L428.526867 570.219983C428.525843 552.97625 414.549527 539.004027 397.310911 539.004027z"
fill={getIconColor(color, 1, '#999999')}
/>
<Path
d="M627.033943 539.004027c-17.238616 0-31.215956 13.972223-31.215956 31.215956l0 28.716019c0 17.243733 13.977339 31.215956 31.215956 31.215956 17.238616 0 31.215956-13.972223 31.215956-31.215956L658.249899 570.219983C658.249899 552.97625 644.272559 539.004027 627.033943 539.004027z"
fill={getIconColor(color, 2, '#999999')}
/>
<Path
d="M511.88232 761.598708c42.195008 0 72.079642-29.081339 83.089393-46.264697 9.206686-14.378475 5.02955-33.298384-9.221012-42.698474-14.246469-9.419534-33.537837-5.41636-43.135426 8.68787-0.121773 0.183172-12.335956 17.84339-30.732955 17.84339-17.807574 0-29.046546-16.237823-29.859052-17.447371-9.175987-14.419408-28.268834-18.778692-42.794665-9.734712-14.647605 9.094122-19.144012 28.340465-10.044774 42.982954C439.944917 732.294288 469.469348 761.598708 511.88232 761.598708z"
fill={getIconColor(color, 3, '#999999')}
/>
<Path
d="M889.99703 326.56566c-10.898211-18.908652-44.732807-72.77856-103.051027-123.781164-12.985756-11.360745-32.694633-10.029424-44.049239 2.947122-11.350512 12.976546-10.029424 32.69975 2.947122 44.039006 60.703547 53.088102 91.617627 110.648053 92.577488 112.451118 2.573615 5.492084 6.757914 10.29753 12.374842 13.610996 33.553187 19.814278 50.786687 37.352723 45.746904 73.243141-20.927635 149.880589-79.161944 246.209518-183.300746 303.174927-1.590218 0.873903-3.236718 1.706875-4.88731 2.550079-12.493545 6.401804-35.752272 18.300808-40.162722 46.600341-8.56098 54.983265-38.678928 59.270918-100.546997 63.11241l-2.373047 0.14224c-43.617403 2.76395-91.736331 1.077541-147.091056-5.131881-44.51689-5.009084-55.17667-20.74958-58.549487-46.285163-4.964058-37.220716-32.633235-52.188616-49.160653-61.131289-2.402723-1.300622-4.816702-2.601244-7.138584-3.953032-98.219999-57.421804-153.909345-151.373593-175.253466-295.655673-5.131881-34.778084 6.343475-57.314357 37.969776-73.561389 0.813528 0.064468 1.626034 0.267083 2.436492 0.267083 11.700483 0 22.924105-6.614651 28.248368-17.904789 0.954745-2.022053 98.138134-202.597231 315.423903-202.597231 17.238616 0 31.215956-13.977339 31.215956-31.215956 0-17.238616-13.977339-31.215956-31.215956-31.215956-219.425502 0-332.189751 167.32489-363.060853 221.73715-80.433914 35.284621-89.613994 97.308233-82.779332 143.625118 24.00574 162.256454 91.223654 273.605471 205.539236 340.436576 2.926656 1.706875 5.918803 3.332908 8.901741 4.948709 15.536858 8.403391 16.400528 10.04989 16.984836 14.429641 7.767917 58.833967 45.939285 92.51916 113.472378 100.120278 40.558741 4.542456 77.643358 6.797823 112.211664 6.797823 15.739473 0 30.962176-0.467651 45.746904-1.401929l2.337231-0.14224c55.821353-3.464915 139.881864-8.677637 157.806095-112.394836 2.02717-1.25969 5.187139-2.87549 7.483438-4.044106 2.194992-1.127683 4.369517-2.246157 6.442736-3.384074 121.199363-66.303079 191.567014-180.557262 215.126592-349.266685C969.146695 380.950292 918.747841 344.137875 889.99703 326.56566z"
fill={getIconColor(color, 4, '#999999')}
/>
<Path
d="M607.719038 172.250062c18.407232 5.395894 36.631292 12.498662 54.175877 21.10569 4.424776 2.174526 9.109472 3.200902 13.722536 3.200902 11.537777 0 22.629393-6.42227 28.05087-17.477047 7.590885-15.47546 1.199315-34.177404-14.277168-41.773406-20.723997-10.166547-42.291199-18.564821-64.092737-24.961508-16.568351-4.877077-33.892925 4.618181-38.745443 21.160949C581.701478 150.047388 591.177294 167.392428 607.719038 172.250062z"
fill={getIconColor(color, 5, '#999999')}
/>
<Path
d="M422.688905 322.749752c4.028757 0 8.129144-0.793062 12.07706-2.448772 15.897062-6.67605 23.366174-24.976858 16.685008-40.868803l-10.791787-25.677822c-6.670933-15.902178-24.966625-23.350824-40.868803-16.685008-15.897062 6.67605-23.366174 24.976858-16.685008 40.868803l10.791787 25.677822C398.916479 315.566142 410.495188 322.749752 422.688905 322.749752z"
fill={getIconColor(color, 6, '#999999')}
/>
<Path
d="M644.058688 363.426173l21.196765-25.677822c10.973936-13.300934 9.094122-32.973996-4.201695-43.947931-13.295817-10.984169-32.963763-9.083889-43.942815 4.196579l-21.196765 25.677822c-10.973936 13.300934-9.094122 32.973996 4.201695 43.947931 5.817496 4.806469 12.853749 7.143701 19.85521 7.143701C628.963898 374.766452 637.886105 370.894262 644.058688 363.426173z"
fill={getIconColor(color, 7, '#999999')}
/>
<Path
d="M310.776046 368.872209c5.522784 3.983731 11.904121 5.89322 18.22406 5.89322 9.69378 0 19.250436-4.501524 25.352411-12.966313 10.075473-13.992689 6.909363-33.502022-7.077186-43.572378l-21.196765-15.272845c-13.987572-10.089799-33.486672-6.909363-43.577494 7.072069-10.075473 13.992689-6.909363 33.502022 7.077186 43.572378L310.776046 368.872209z"
fill={getIconColor(color, 8, '#999999')}
/>
</Svg>
);
};
export default React.memo(IconIcon);
@@ -0,0 +1,32 @@
/* eslint-disable */
/* tslint:disable */
import React from 'react';
import type { FunctionComponent } from 'react';
import type { ViewProps } from 'react-native';
import { Svg, GProps, Path } from 'react-native-svg';
import { getIconColor } from './helper';
interface Props extends Omit<GProps, 'color'>, ViewProps {
size?: number;
color?: string | string[];
}
const IconYule1: FunctionComponent<Props> = ({
size = 18,
color,
...rest
}) => {
return (
<Svg viewBox="0 0 1024 1024" width={size} height={size} {...rest}>
<Path
d="M903.966695 700.247408 602.093413 325.978283c0.403183-5.686513 0.868787-11.330046 0.868787-17.141402 0-135.12656-109.933785-245.060345-245.071601-245.060345-135.147026 0-245.081835 109.934808-245.081835 245.060345 0 135.136793 109.934808 245.070578 245.081835 245.070578 5.78168 0 11.455913-0.466628 17.132192-0.847298l374.235356 301.883515c5.028527 4.02978 11.085476 6.024204 17.119913 6.024204 7.011694 0 14.002922-2.671852 19.285229-7.976672l21.341051-21.340027c0.040932 8.228405-1.01819 16.927531-4.158716 25.454742-7.021927 18.96698-23.557532 34.07098-49.143257 44.804439-30.124088 12.68695-76.985372-9.289574-131.244136-34.70543-99.908454-46.840818-236.712213-110.995977-383.123794 3.734044-11.860119 9.29162-13.928221 26.476001-4.647857 38.311561 9.29162 11.837606 26.435069 13.916964 38.293141 4.626367 119.989815-94.065376 229.604328-42.642193 326.332347 2.714831 47.416939 22.234397 92.857874 43.532469 134.733611 43.532469 14.023388 0 27.665083-2.376117 40.828894-7.933693 40.030715-16.844643 66.655096-42.472324 79.118965-76.203566 13.035898-35.217083 6.576789-70.261227 0.044002-91.3925l47.985898-47.987944C911.876851 726.764342 912.726196 711.064778 903.966695 700.247408zM167.350984 308.836881c0-105.054661 85.473697-190.517101 190.539614-190.517101 105.055684 0 190.529381 85.46244 190.529381 190.517101s-85.473697 190.548824-190.529381 190.548824C252.824681 499.385704 167.350984 413.891542 167.350984 308.836881zM764.427638 797.111526 443.481975 538.209942c66.295915-24.865317 118.916367-77.476559 143.749962-143.752008l258.902608 320.93543-38.642089 38.651298c-3.53143 2.249227-6.417153 5.133927-8.528234 8.529257L764.427638 797.111526zM600.172667 515.89061c-10.660804-10.649548-27.918863-10.649548-38.568411 0-10.648524 10.648524-10.648524 27.918863 0 38.568411l59.093886 59.104119c5.325285 5.303796 12.304234 7.977695 19.284205 7.977695 6.970762 0 13.948687-2.673899 19.273972-7.977695 10.660804-10.649548 10.660804-27.91784 0-38.567387L600.172667 515.89061z"
fill={getIconColor(color, 0, '#272636')}
/>
</Svg>
);
};
export default React.memo(IconYule1);
@@ -4,6 +4,8 @@
import React, { FunctionComponent } from 'react';
import { ViewProps } from 'react-native';
import { GProps } from 'react-native-svg';
import IconIcon from './IconIcon';
import IconYule1 from './IconYule1';
import IconZhichu from './IconZhichu';
import IconShouru from './IconShouru';
import IconJiaotong from './IconJiaotong';
@@ -15,6 +17,8 @@ import IconQuxian1 from './IconQuxian1';
import IconWeixin from './IconWeixin';
import IconZhiwen from './IconZhiwen';
import IconQq from './IconQq';
export { default as IconIcon } from './IconIcon';
export { default as IconYule1 } from './IconYule1';
export { default as IconZhichu } from './IconZhichu';
export { default as IconShouru } from './IconShouru';
export { default as IconJiaotong } from './IconJiaotong';
@@ -27,7 +31,7 @@ export { default as IconWeixin } from './IconWeixin';
export { default as IconZhiwen } from './IconZhiwen';
export { default as IconQq } from './IconQq';
export type IconNames = 'zhichu' | 'shouru' | 'jiaotong' | 'shucai' | 'riyongpin' | 'yule' | 'lingshi' | 'quxian1' | 'weixin' | 'zhiwen' | 'QQ';
export type IconNames = 'icon' | 'yule1' | 'zhichu' | 'shouru' | 'jiaotong' | 'shucai' | 'riyongpin' | 'yule' | 'lingshi' | 'quxian1' | 'weixin' | 'zhiwen' | 'QQ';
interface Props extends GProps, ViewProps {
name: IconNames;
@@ -37,28 +41,32 @@ interface Props extends GProps, ViewProps {
let IconFont: FunctionComponent<Props> = ({ name, ...rest }) => {
switch (name) {
case 'icon':
return <IconIcon key="1" {...rest} />;
case 'yule1':
return <IconYule1 key="2" {...rest} />;
case 'zhichu':
return <IconZhichu key="1" {...rest} />;
return <IconZhichu key="3" {...rest} />;
case 'shouru':
return <IconShouru key="2" {...rest} />;
return <IconShouru key="4" {...rest} />;
case 'jiaotong':
return <IconJiaotong key="3" {...rest} />;
return <IconJiaotong key="5" {...rest} />;
case 'shucai':
return <IconShucai key="4" {...rest} />;
return <IconShucai key="6" {...rest} />;
case 'riyongpin':
return <IconRiyongpin key="5" {...rest} />;
return <IconRiyongpin key="7" {...rest} />;
case 'yule':
return <IconYule key="6" {...rest} />;
return <IconYule key="8" {...rest} />;
case 'lingshi':
return <IconLingshi key="7" {...rest} />;
return <IconLingshi key="9" {...rest} />;
case 'quxian1':
return <IconQuxian1 key="8" {...rest} />;
return <IconQuxian1 key="10" {...rest} />;
case 'weixin':
return <IconWeixin key="9" {...rest} />;
return <IconWeixin key="11" {...rest} />;
case 'zhiwen':
return <IconZhiwen key="10" {...rest} />;
return <IconZhiwen key="12" {...rest} />;
case 'QQ':
return <IconQq key="11" {...rest} />;
return <IconQq key="13" {...rest} />;
}
return null;
@@ -0,0 +1,224 @@
'use client';
import React from 'react';
import { createFab } from '@gluestack-ui/core/fab/creator';
import { Pressable, Text } from 'react-native';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import {
withStyleContext,
useStyleContext,
} from '@gluestack-ui/utils/nativewind-utils';
import { cssInterop } from 'nativewind';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
const SCOPE = 'FAB';
const Root = withStyleContext(Pressable, SCOPE);
const UIFab = createFab({
Root: Root,
Label: Text,
Icon: UIIcon,
});
cssInterop(PrimitiveIcon, {
className: {
target: 'style',
nativeStyleToProp: {
height: true,
width: true,
fill: true,
color: 'classNameColor',
stroke: true,
},
},
});
const fabStyle = tva({
base: 'group/fab bg-primary-500 rounded-full z-20 p-4 flex-row items-center justify-center absolute hover:bg-primary-600 active:bg-primary-700 disabled:opacity-40 disabled:pointer-events-all disabled:cursor-not-allowed data-[focus=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[focus-visible=true]:web:ring-indicator-info shadow-hard-2',
variants: {
size: {
sm: 'px-2.5 py-2.5',
md: 'px-3 py-3',
lg: 'px-4 py-4',
},
placement: {
'top right': 'top-4 right-4',
'top left': 'top-4 left-4',
'bottom right': 'bottom-4 right-4',
'bottom left': 'bottom-4 left-4',
'top center': 'top-4 self-center',
'bottom center': 'bottom-4 self-center',
},
},
});
const fabLabelStyle = tva({
base: 'text-typography-50 font-normal font-body tracking-md text-left mx-2',
variants: {
isTruncated: {
true: '',
},
bold: {
true: 'font-bold',
},
underline: {
true: 'underline',
},
strikeThrough: {
true: 'line-through',
},
size: {
'2xs': 'text-2xs',
'xs': 'text-xs',
'sm': 'text-sm',
'md': 'text-base',
'lg': 'text-lg',
'xl': 'text-xl',
'2xl': 'text-2xl',
'3xl': 'text-3xl',
'4xl': 'text-4xl',
'5xl': 'text-5xl',
'6xl': 'text-6xl',
},
sub: {
true: 'text-xs',
},
italic: {
true: 'italic',
},
highlight: {
true: 'bg-yellow-500',
},
},
parentVariants: {
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
},
},
});
const fabIconStyle = tva({
base: 'text-typography-50 fill-none',
variants: {
size: {
'2xs': 'h-3 w-3',
'xs': 'h-3.5 w-3.5',
'sm': 'h-4 w-4',
'md': 'w-[18px] h-[18px]',
'lg': 'h-5 w-5',
'xl': 'h-6 w-6',
},
},
});
type IFabProps = Omit<React.ComponentPropsWithoutRef<typeof UIFab>, 'context'> &
VariantProps<typeof fabStyle>;
const Fab = React.forwardRef<React.ComponentRef<typeof UIFab>, IFabProps>(
function Fab(
{ size = 'md', placement = 'bottom right', className, ...props },
ref
) {
return (
<UIFab
ref={ref}
{...props}
className={fabStyle({ size, placement, class: className })}
context={{ size }}
/>
);
}
);
type IFabLabelProps = React.ComponentPropsWithoutRef<typeof UIFab.Label> &
VariantProps<typeof fabLabelStyle>;
const FabLabel = React.forwardRef<
React.ComponentRef<typeof UIFab.Label>,
IFabLabelProps
>(function FabLabel(
{
size,
isTruncated = false,
bold = false,
underline = false,
strikeThrough = false,
className,
...props
},
ref
) {
const { size: parentSize } = useStyleContext(SCOPE);
return (
<UIFab.Label
ref={ref}
{...props}
className={fabLabelStyle({
parentVariants: {
size: parentSize,
},
size,
isTruncated,
bold,
underline,
strikeThrough,
class: className,
})}
/>
);
});
type IFabIconProps = React.ComponentPropsWithoutRef<typeof UIFab.Icon> &
VariantProps<typeof fabIconStyle> & {
height?: number;
width?: number;
};
const FabIcon = React.forwardRef<
React.ComponentRef<typeof UIFab.Icon>,
IFabIconProps
>(function FabIcon({ size, className, ...props }, ref) {
const { size: parentSize } = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UIFab.Icon
ref={ref}
{...props}
className={fabIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props.height !== undefined || props.width !== undefined) &&
size === undefined
) {
return (
<UIFab.Icon
ref={ref}
{...props}
className={fabIconStyle({ class: className })}
/>
);
}
return (
<UIFab.Icon
ref={ref}
{...props}
className={fabIconStyle({
parentVariants: {
size: parentSize,
},
size,
class: className,
})}
/>
);
});
Fab.displayName = 'Fab';
FabLabel.displayName = 'FabLabel';
FabIcon.displayName = 'FabIcon';
export { Fab, FabLabel, FabIcon };
@@ -0,0 +1,13 @@
import type { MoneyCategory } from '@/types/money'
import alovaInstance from '../index'
const API_PREFIX = '/money'
export interface MoneyCategoryResult {
income: MoneyCategory[]
expense: MoneyCategory[]
}
export function getMoneyCategoryApi() {
return alovaInstance.Get<ResponseData<MoneyCategoryResult>>(`${API_PREFIX}/category`)
}
+1 -1
View File
@@ -11,7 +11,7 @@ declare global {
interface UserInfo {
account: string
id: string
id: number
seqNo: number
username: string
gender: number
@@ -0,0 +1,9 @@
export interface MoneyCategory {
id: string
user_id: number
name: string
label: string
icon: string
sort_number: number
type: number
}