This commit is contained in:
oneao committed 2026-01-20 17:32:01 +08:00
1 parent e51f4948d4
commit 2130be4e49
6 files changed
+188 -90

No files matched your search

+10 -17
View File
@@ -1,5 +1,7 @@
import { Text, View } from 'react-native' import { Text, View } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import { AButton, ACell, ACellGroup } from '@/components/common/ao' import { AButton, ACell, ACellGroup } from '@/components/common/ao'
import { Grid, GridItem } from '@/components/ui/grid'
import { useMoneyCategoryStore } from '@/store/moneyCategory' import { useMoneyCategoryStore } from '@/store/moneyCategory'
export default function Demo() { export default function Demo() {
@@ -15,22 +17,13 @@ export default function Demo() {
} }
return ( return (
<View <ScrollView>
className="flex-1" <Grid className="gap-4 p-4" _extra={{ className: 'grid-cols-12' }}>
style={{ <GridItem className="bg-background-50 p-6 rounded-md" _extra={{ className: 'col-span-3' }} />
marginTop: 34, <GridItem className="bg-background-50 p-6 rounded-md" _extra={{ className: 'col-span-3' }} />
}} <GridItem className="bg-background-50 p-6 rounded-md" _extra={{ className: 'col-span-3' }} />
> <GridItem className="bg-background-50 p-6 rounded-md" _extra={{ className: 'col-span-3' }} />
<ACellGroup> </Grid>
<ACell leftIcon="Zap" title="测试" rightText="测试右边" isLink /> </ScrollView>
<ACell leftIcon="Airplay" title="测试" rightText="测试右边" isLink />
<ACell leftIcon="Airplay" title="测试" rightText="测试右边" isLink />
<ACell leftIcon="Airplay" title="测试" rightText="测试右边" isLink />
<ACell leftIcon="Airplay" title="测试" rightText="测试右边" isLink />
</ACellGroup>
<AButton onPress={() => resetCategory()}>重置记账分类</AButton>
<AButton onPress={() => getCategories()}>获取记账分类</AButton>
</View>
) )
} }
@@ -238,7 +238,7 @@ export default function MoneyCategorySetting() {
> >
<LucideIcon icon="Trash" size={18} color="#fff" /> <LucideIcon icon="Trash" size={18} color="#fff" />
<Text className="text-base text-white "> <Text className="text-base text-white ">
拖到这里删除 {draggingDeleteZone ? '松手删除(将删除该类下所有记录)' : '拖到这里删除'}
</Text> </Text>
</View> </View>
</Sortable.BaseZone> </Sortable.BaseZone>
@@ -0,0 +1,111 @@
import type { ViewStyle } from 'react-native'
import type { MoneyCategoryItem } from '@/types/money/category'
import React, { useEffect, useMemo, useRef } from 'react'
import { FlatList, Pressable, Text, View } from 'react-native'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
interface CategoryGridProps {
data: MoneyCategoryItem[]
selectedCategory?: MoneyCategoryItem | null
onSelectCategory?: (item: MoneyCategoryItem | null) => void
className?: string
style?: ViewStyle
}
export default function CategoryGrid({
data,
selectedCategory,
onSelectCategory,
className,
style,
}: CategoryGridProps) {
const flatListRef = useRef<FlatList<any>>(null)
// 每行 4 个,补齐空项
const memoItems = useMemo(() => {
const items = [...data]
const remainder = items.length % 4
if (remainder > 0) {
const emptyCount = 4 - remainder
for (let i = 0; i < emptyCount; i++) {
items.push({ id: `empty-${i}` } as any)
}
}
return items
}, [data])
// 自动滚动到选中行
useEffect(() => {
if (!selectedCategory || !flatListRef.current)
return
// 找到选中项下标
const itemIndex = data.findIndex(item => item.id === selectedCategory.id)
if (itemIndex === -1 || !flatListRef.current)
return
setTimeout(() => {
if (flatListRef.current) {
flatListRef.current.scrollToIndex({
index: Math.floor(itemIndex / 4),
animated: true,
viewPosition: 0.5,
})
}
}, 0)
}, [selectedCategory])
return (
<FlatList
ref={flatListRef}
data={memoItems}
numColumns={4}
keyExtractor={item => item.id.toString()}
showsVerticalScrollIndicator={false}
className={`${className}`}
style={[style]}
getItemLayout={(data, index) => {
return { length: 80, offset: 80 * index, index }
}}
renderItem={({ item }) => {
if (!('name' in item))
return <View style={{ flex: 1 / 4, margin: 4 }} />
const isSelected = selectedCategory?.id === item.id
return (
<Pressable
style={{ flex: 1 / 4, height: 80, alignItems: 'center' }}
onPress={() => onSelectCategory?.(item)}
onLayout={(event) => {
const { height } = event.nativeEvent.layout
console.log('Pressable 高度:', height)
}}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
marginTop: 4,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: isSelected ? '#333' : '#F3F4F6',
}}
>
<MoneyCategoryIcon
name={item.icon}
size={28}
color={isSelected ? '#fff' : '#000'}
/>
</View>
<Text style={{ marginBottom: 4, fontSize: 14, textAlign: 'center' }}>
{item.name}
</Text>
</Pressable>
)
}}
/>
)
}
@@ -1,89 +1,83 @@
import type { ViewStyle } from 'react-native' import type { FlatList } from 'react-native'
import type { MoneyCategoryItem } from '@/types/money/category' import type { MoneyCategoryItem } from '@/types/money/category'
import React, { useMemo } from 'react' import React, { useEffect, useRef } from 'react'
import { FlatList, Pressable, Text, View } from 'react-native' import { Pressable, Text, View } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon' import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import { Grid, GridItem } from '@/components/ui/grid'
interface CategoryGridProps { interface CategoryGridProps {
data: MoneyCategoryItem[] data: MoneyCategoryItem[]
selectedCategory?: MoneyCategoryItem | null selectedCategory?: MoneyCategoryItem | null
onSelectCategory?: (item: MoneyCategoryItem | null) => void onSelectCategory?: (item: MoneyCategoryItem | null) => void
className?: string
style?: ViewStyle
} }
export default function CategoryGrid({ export default function CategoryGrid({
data, data,
selectedCategory, selectedCategory,
onSelectCategory, onSelectCategory,
className,
style,
}: CategoryGridProps) { }: CategoryGridProps) {
// 计算补齐数组,让每行4个 const flatListRef = useRef<FlatList<any>>(null)
const memoItems = useMemo(() => {
const items = [...data] // 自动滚动到选中行
const remainder = items.length % 4 useEffect(() => {
if (remainder > 0) { if (!selectedCategory || !flatListRef.current)
const emptyCount = 4 - remainder return
for (let i = 0; i < emptyCount; i++) {
items.push({ id: `empty-${i}` } as any) // 找到选中项下标
const itemIndex = data.findIndex(item => item.id === selectedCategory.id)
if (itemIndex === -1 || !flatListRef.current)
return
setTimeout(() => {
if (flatListRef.current) {
flatListRef.current.scrollToIndex({
index: Math.floor(itemIndex / 4),
animated: true,
viewPosition: 0.5,
})
} }
} }, 0)
return items }, [selectedCategory])
}, [data])
return ( return (
<FlatList <ScrollView>
data={memoItems} <Grid className="gap-4 px-2 py-4 justify-start" _extra={{ className: 'grid-cols-12' }}>
numColumns={4} {data.map((item) => {
keyExtractor={item => item.id.toString()} const isSelected = selectedCategory?.id === item.id
showsVerticalScrollIndicator={false} return (
className={`${className}`} <GridItem
style={[style]} _extra={{ className: 'col-span-3' }}
renderItem={({ item }) => { key={item.id}
if (!('name' in item)) {
return <View style={{ flex: 1 / 4, margin: 4 }} />
}
const isSelected = selectedCategory?.id === item.id
return (
<Pressable
style={{ flex: 1 / 4, margin: 4, alignItems: 'center' }}
onPress={() => onSelectCategory?.(item)}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
marginTop: 4,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: isSelected ? '#333' : '#F3F4F6',
}}
> >
<MoneyCategoryIcon <Pressable
name={item.icon} style={{ alignItems: 'center' }}
size={28} onPress={() => onSelectCategory?.(item)}
color={ >
isSelected ? '#fff' : '#000' <View
} style={{
/> width: 46,
</View> height: 46,
<Text borderRadius: 28,
style={{ justifyContent: 'center',
marginBottom: 4, alignItems: 'center',
fontSize: 14, backgroundColor: isSelected ? '#333' : '#F3F4F6',
textAlign: 'center', }}
}} >
> <MoneyCategoryIcon
{item.name} name={item.icon}
</Text> size={28}
</Pressable> color={isSelected ? '#fff' : '#000'}
) />
}} </View>
/> <Text style={{ fontSize: 14, textAlign: 'center' }}>
{item.name}
</Text>
</Pressable>
</GridItem>
)
})}
</Grid>
</ScrollView>
) )
} }
+2 -2
View File
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
} }
const alovaInstance = createAlova({ const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8086/app', // baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app', baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(), requestAdapter: axiosRequestAdapter(),
timeout: 5000, timeout: 5000,
cacheFor: null, cacheFor: null,
@@ -107,7 +107,7 @@ export const useMoneyCategoryStore = create<MoneyCategoryStore>()(
try { try {
await queryMoneyCategoryApi().then((res) => { await queryMoneyCategoryApi().then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
set({ categories: res.data.categories || [] }) get().setCategories(res.data.categories || [])
} }
}).catch((err) => { }).catch((err) => {
console.log(err) console.log(err)