This commit is contained in:
oneao committed 2026-01-14 22:25:14 +08:00
1 parent cb4562361a
commit b38583a9fe
11 files changed
+174 -85

No files matched your search

+1
View File
@@ -63,6 +63,7 @@
"@types/big.js": "^6.2.2",
"@types/react": "~19.1.0",
"babel-plugin-module-resolver": "^5.0.2",
"baseline-browser-mapping": "^2.9.14",
"eslint": "^9.38.0",
"react-native-iconfont-cli": "^2.2.4",
"react-native-svg-transformer": "^1.5.2",
+7 -4
View File
@@ -162,6 +162,9 @@ importers:
babel-plugin-module-resolver:
specifier: ^5.0.2
version: 5.0.2
baseline-browser-mapping:
specifier: ^2.9.14
version: 2.9.14
eslint:
specifier: ^9.38.0
version: 9.39.1(jiti@1.21.7)
@@ -2509,8 +2512,8 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.8.28:
resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==}
baseline-browser-mapping@2.9.14:
resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==}
hasBin: true
better-opn@3.0.2:
@@ -9105,7 +9108,7 @@ snapshots:
base64-js@1.5.1: {}
baseline-browser-mapping@2.8.28: {}
baseline-browser-mapping@2.9.14: {}
better-opn@3.0.2:
dependencies:
@@ -9146,7 +9149,7 @@ snapshots:
browserslist@4.28.0:
dependencies:
baseline-browser-mapping: 2.8.28
baseline-browser-mapping: 2.9.14
caniuse-lite: 1.0.30001754
electron-to-chromium: 1.5.252
node-releases: 2.0.27
@@ -1,37 +1,67 @@
import { useEffect, useState } from 'react'
import { Pressable, ScrollView, Text, View } from 'react-native'
import { Pressable, Text, View } from 'react-native'
import { AButton } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import {
Drawer,
DrawerBackdrop,
DrawerBody,
DrawerContent,
DrawerFooter,
DrawerHeader,
} from '@/components/ui/drawer'
import { Input, InputField } from '@/components/ui/input'
import { useMoneyCategoryStore } from '@/store/moneyCategory'
import { MoneyCategoryEnum } from '@/types/money/category'
interface CreateCategoryProps {
open: boolean
onClose?: () => void
type: MoneyCategoryEnum
}
interface CreateCategoryForm {
icon: string
name: string
}
export default function CreateCategory({
open,
onClose,
type,
}: CreateCategoryProps) {
const moneyCategoryStore = useMoneyCategoryStore()
const [isOpen, setIsOpen] = useState(open)
const [form, setForm] = useState<CreateCategoryForm>({
icon: '',
name: '',
})
const [confirmDisabled, setConfirmDisabled] = useState(true)
const [confirmLoading, setConfirmLoading] = useState(false)
useEffect(() => {
setIsOpen(open)
}, [open])
useEffect(() => {
if (form.name && form.name.length < 6) {
setConfirmDisabled(false)
}
else {
setConfirmDisabled(true)
}
}, [form.name])
function handleClose() {
setIsOpen(false)
onClose && onClose()
setForm({
icon: '',
name: '',
})
}
return (
@@ -45,60 +75,65 @@ export default function CreateCategory({
>
<DrawerHeader className="px-4 flex flex-col gap-4">
<View className="flex flex-row items-center justify-between w-full">
<Pressable>
<Text className="text-gray-600">取消</Text>
</Pressable>
<AButton size="xs" variant="outline" onPress={handleClose}>取消</AButton>
<Text className="text-xl font-medium">类别</Text>
<Text className="text-lg font-medium">{type === MoneyCategoryEnum.Expense ? '添加支出分类' : '添加收入分类'}</Text>
<Pressable>
<Text className="text-gray-600">确认</Text>
</Pressable>
<AButton size="xs" disabled={confirmDisabled} loading={confirmLoading}>确认</AButton>
</View>
<Input
variant="outline"
>
<InputField placeholder="请输入名称" />
<InputField placeholder="请输入名称(不超过5个字符)" />
</Input>
</DrawerHeader>
<DrawerBody>
<ScrollView className="flex-1">
{Object.entries(moneyCategoryStore.ICON_GROUPS).map(([groupName, icons]) => (
<View key={groupName} className="mb-4">
<Text className="text-center mb-4">
{groupName}
</Text>
<DrawerBody className="mb-16">
{Object.entries(moneyCategoryStore.ICON_GROUPS).map(([groupName, icons]) => (
<View key={groupName} className="mb-2">
<Text className="text-center text-sm mb-4">
{groupName}
</Text>
<View className="flex flex-row flex-wrap">
{icons.map(icon => (
<Pressable
key={icon}
className="w-1/5 mb-3 flex items-center"
onPress={() => {
console.log('select icon:', icon)
<View className="flex flex-row flex-wrap">
{icons.map(icon => (
<Pressable
key={icon}
className="w-1/5 mb-3 flex items-center"
onPress={() => {
setForm(val => ({
...val,
icon,
}))
}}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: icon === form.icon ? '#333' : '#F3F4F6',
}}
>
<View
style={{
width: 46,
height: 46,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F3F4F6',
}}
>
<MoneyCategoryIcon name={icon} size={28} />
</View>
</Pressable>
))}
</View>
<MoneyCategoryIcon
name={icon}
size={32}
color={
icon === form.icon ? '#fff' : '#000'
}
/>
</View>
</Pressable>
))}
</View>
))}
</ScrollView>
</View>
))}
</DrawerBody>
<DrawerFooter />
</DrawerContent>
</Drawer>
)
@@ -9,10 +9,11 @@ import { AButton, ATab, LucideIcon } from '@/components/common/ao'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import CustomHeader from '@/components/custom/Header'
import { useMoneyCategoryStore } from '@/store/moneyCategory'
import { MoneyCategoryEnum } from '@/types/money/category'
import CreateCategory from './components/CreateCategory'
export default function MoneyCategorySetting() {
const [selectedTab, setSelectedTab] = useState(0)
const [selectedTab, setSelectedTab] = useState(MoneyCategoryEnum.Expense)
const moneyCategoryStore = useMoneyCategoryStore()
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
@@ -25,7 +26,7 @@ export default function MoneyCategorySetting() {
const [openCreateCategory, setOpenCreateCategory] = useState<boolean>(false)
useEffect(() => {
setCategories(selectedTab === 0 ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
setCategories(selectedTab === MoneyCategoryEnum.Expense ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
}, [selectedTab])
const renderItem = useCallback<SortableGridRenderItem<MoneyCategoryItem>>(
@@ -69,18 +70,18 @@ export default function MoneyCategorySetting() {
<View className="flex-1">
<View className="pt-2 mb-4 px-3 flex flex-row items-center justify-between">
<ATab
defaultIndex={selectedTab}
onChange={(index) => {
setSelectedTab(index)
defaultKey={selectedTab}
onChange={(key) => {
setSelectedTab(key as MoneyCategoryEnum)
}}
data={[
{
key: 'income',
label: '收入',
key: MoneyCategoryEnum.Expense,
label: '支出',
},
{
key: 'expense',
label: '支出',
key: MoneyCategoryEnum.Income,
label: '收入',
},
]}
/>
@@ -189,6 +190,7 @@ export default function MoneyCategorySetting() {
</View>
<CreateCategory
type={selectedTab}
open={openCreateCategory}
onClose={() => setOpenCreateCategory(false)}
/>
@@ -4,14 +4,14 @@ import PagerView from 'react-native-pager-view'
interface TabItem {
label: string
key: string
key: string | number
content?: React.ReactNode
}
interface TabProps {
data?: TabItem[]
defaultIndex?: number
onChange?: (index: number) => void
defaultKey?: TabItem['key']
onChange?: (key: TabItem['key']) => void
tabBarClass?: string
tabItemClass?: string
activeTabItemClass?: string
@@ -19,47 +19,66 @@ interface TabProps {
export default function Tab({
data,
defaultIndex = 0,
defaultKey,
onChange,
tabBarClass = 'flex flex-row gap-2 overflow-hidden',
tabItemClass = 'items-center justify-center rounded-md px-4 py-2 bg-gray-200',
activeTabItemClass = 'items-center justify-center rounded-md px-4 py-2 bg-gray-800',
}: TabProps) {
const [selectedIndex, setSelectedIndex] = useState(defaultIndex)
const pagerRef = useRef<PagerView>(null)
if (!data || data.length === 0)
return null
// 判断是否至少有一个 tab 含 content
// 默认选中 key(没传就用第一个)
const initialKey = defaultKey ?? data[0].key
const [selectedKey, setSelectedKey] = useState(initialKey)
const pagerRef = useRef<PagerView>(null)
// 是否有 content
const hasContent = useMemo(
() => data.some(item => item.content),
[data],
)
const handlePress = (index: number) => {
setSelectedIndex(index)
onChange?.(index)
if (hasContent)
pagerRef.current?.setPage(index)
// key -> index
const getIndexByKey = (key: TabItem['key']) =>
data.findIndex(item => item.key === key)
// index -> key
const getKeyByIndex = (index: number) =>
data[index]?.key
const handlePress = (key: TabItem['key']) => {
setSelectedKey(key)
onChange?.(key)
if (hasContent) {
const index = getIndexByKey(key)
if (index !== -1)
pagerRef.current?.setPage(index)
}
}
const onPageSelected = (e: any) => {
const index = e.nativeEvent.position
setSelectedIndex(index)
onChange?.(index)
const key = getKeyByIndex(index)
if (key !== undefined) {
setSelectedKey(key)
onChange?.(key)
}
}
return (
<View className="flex-1">
{/* tab bar */}
<View className={tabBarClass}>
{data.map((item, index) => {
const isActive = index === selectedIndex
{data.map((item) => {
const isActive = item.key === selectedKey
return (
<Pressable
key={item.key}
onPress={() => handlePress(index)}
onPress={() => handlePress(item.key)}
className={isActive ? activeTabItemClass : tabItemClass}
>
<Text className={isActive ? 'text-white' : 'text-gray-700'}>
@@ -74,7 +93,7 @@ export default function Tab({
<PagerView
ref={pagerRef}
style={{ flex: 1, marginTop: 16 }}
initialPage={defaultIndex}
initialPage={getIndexByKey(initialKey)}
onPageSelected={onPageSelected}
>
{data.map(item => (
+2 -2
View File
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app',
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
@@ -28,3 +28,8 @@ export interface UpdateMoneyCategorySortItem {
export interface UpdateMoneyCategorySortParams {
data: UpdateMoneyCategorySortItem[]
}
export enum MoneyCategoryEnum {
Expense = 0, // 支出
Income = 1, // 收入
}