u
This commit is contained in:
1 parent
069b4506e4
commit
bb780a42ab
10 files changed
+389
-246
No files matched your search
@@ -1,49 +1,140 @@
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import type Animated from 'react-native-reanimated'
|
||||
import type { SortableGridRenderItem } from 'react-native-sortables'
|
||||
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
|
||||
import { Platform, Animated as RNAnimated, Text, Vibration, View } from 'react-native'
|
||||
import { useAnimatedRef } from 'react-native-reanimated'
|
||||
import Sortable from 'react-native-sortables'
|
||||
import { AButton, ASegmentedTabs, LucideIcon } from '@/components/common/ao'
|
||||
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
|
||||
const DATA = [
|
||||
'Poland',
|
||||
'Germany',
|
||||
'France',
|
||||
'Italy',
|
||||
'Spain',
|
||||
'Portugal',
|
||||
'Greece',
|
||||
'Great Britain',
|
||||
'United States',
|
||||
'Canada',
|
||||
'Australia',
|
||||
'New Zealand',
|
||||
]
|
||||
// 生成 100 条假数据
|
||||
function generateFakeCategories(): MoneyCategoryItem[] {
|
||||
return Array.from({ length: 100 }, (_, index) => ({
|
||||
id: index + 1,
|
||||
name: `类别 ${index + 1}`,
|
||||
icon: 'Coin', // 默认图标
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Example() {
|
||||
const [selectedTab, setSelectedTab] = useState(0)
|
||||
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
|
||||
const deleteTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const [categories, setCategories] = useState<MoneyCategoryItem[]>(generateFakeCategories())
|
||||
const [draggingId, setDraggingId] = useState<number | null>(null)
|
||||
const [isInDeleteZone, setIsInDeleteZone] = useState(false)
|
||||
|
||||
const renderItem = useCallback<SortableGridRenderItem<MoneyCategoryItem>>(
|
||||
({ item }) => (
|
||||
<View className="flex flex-col items-center justify-center">
|
||||
<View
|
||||
style={{
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<MoneyCategoryIcon size={24} name={item.icon} />
|
||||
</View>
|
||||
<Text style={{ marginBottom: 4, fontSize: 14 }}>{item.name}</Text>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
// 拖入删除区时启动延迟删除
|
||||
const handleItemEnter = () => {
|
||||
if (!draggingId)
|
||||
return
|
||||
setIsInDeleteZone(true)
|
||||
Vibration.vibrate(50)
|
||||
|
||||
// 延迟 2 秒删除
|
||||
deleteTimeoutRef.current = setTimeout(() => {
|
||||
setCategories(prev => prev.filter(c => c.id !== draggingId))
|
||||
setDraggingId(null)
|
||||
setIsInDeleteZone(false)
|
||||
deleteTimeoutRef.current = null
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// 拖出删除区取消删除
|
||||
const handleItemLeave = () => {
|
||||
setIsInDeleteZone(false)
|
||||
if (deleteTimeoutRef.current) {
|
||||
clearTimeout(deleteTimeoutRef.current)
|
||||
deleteTimeoutRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
export default function Flex() {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Sortable.Flex
|
||||
gap={10}
|
||||
padding={10}
|
||||
flexDirection="column"
|
||||
width="fill"
|
||||
dragActivationDelay={10}
|
||||
>
|
||||
{DATA.map(item => (
|
||||
<View style={styles.cell} key={item}>
|
||||
<Text style={{ color: '#fff' }}>{item}</Text>
|
||||
</View>
|
||||
))}
|
||||
</Sortable.Flex>
|
||||
<View className="flex-1">
|
||||
<CustomHeader title="类别设置" />
|
||||
|
||||
<View className="flex-1 px-3">
|
||||
<Sortable.MultiZoneProvider>
|
||||
<Sortable.PortalProvider enabled>
|
||||
<RNAnimated.ScrollView ref={scrollableRef}>
|
||||
|
||||
{/* Grid */}
|
||||
<Sortable.Grid
|
||||
columnGap={10}
|
||||
rowGap={10}
|
||||
columns={4}
|
||||
data={categories}
|
||||
renderItem={renderItem}
|
||||
scrollableRef={scrollableRef}
|
||||
autoScrollActivationOffset={200}
|
||||
showDropIndicator
|
||||
onDragStart={data => setDraggingId(Number(data.key))}
|
||||
onDragEnd={() => setDraggingId(null)}
|
||||
/>
|
||||
|
||||
{/* 删除区 */}
|
||||
<Sortable.BaseZone
|
||||
className="mt-4"
|
||||
style={{
|
||||
height: 42,
|
||||
borderRadius: 8,
|
||||
backgroundColor: isInDeleteZone ? '#FF6B6B' : '#FDE2E2',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
minActivationDistance={40}
|
||||
onItemEnter={handleItemEnter}
|
||||
onItemLeave={handleItemLeave}
|
||||
>
|
||||
<Text style={{
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontSize: isInDeleteZone ? 18 : 16,
|
||||
}}
|
||||
>
|
||||
拖到这里删除
|
||||
</Text>
|
||||
</Sortable.BaseZone>
|
||||
|
||||
{/* 新增按钮 */}
|
||||
<View className="mt-3 mb-6 flex-row justify-end">
|
||||
<AButton
|
||||
className="rounded-full px-4 py-2"
|
||||
leftIcon={{ icon: 'Plus', color: '#fff', size: 18 }}
|
||||
>
|
||||
添加分类
|
||||
</AButton>
|
||||
</View>
|
||||
|
||||
</RNAnimated.ScrollView>
|
||||
</Sortable.PortalProvider>
|
||||
</Sortable.MultiZoneProvider>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
cell: {
|
||||
backgroundColor: '#36877F',
|
||||
borderRadius: 9999,
|
||||
padding: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'stretch', // ✅ 关键:让子元素撑满父容器宽度
|
||||
width: '100%',
|
||||
},
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import { Stack, useRouter } from 'expo-router'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { useEffect } from 'react'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import { KeyboardProvider } from 'react-native-keyboard-controller'
|
||||
import { MenuProvider } from 'react-native-popup-menu'
|
||||
import { AButton } from '@/components/common/ao'
|
||||
@@ -15,7 +16,6 @@ import { useUserStore } from '@/store/user'
|
||||
import { ToastProviderWrapper } from '@/utils/toast'
|
||||
import '@/styles/global.css'
|
||||
import 'react-native-reanimated'
|
||||
import 'react-native-gesture-handler'
|
||||
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
@@ -74,25 +74,28 @@ export default function RootLayout() {
|
||||
<GluestackUIProvider>
|
||||
<ToastProvider>
|
||||
<ToastProviderWrapper>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerTitleAlign: 'center',
|
||||
animation: 'slide_from_right',
|
||||
contentStyle: {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="(tabs)"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<GestureHandlerRootView>
|
||||
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerTitleAlign: 'center',
|
||||
animation: 'slide_from_right',
|
||||
contentStyle: {
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="(tabs)"
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</GestureHandlerRootView>
|
||||
</ToastProviderWrapper>
|
||||
</ToastProvider>
|
||||
</GluestackUIProvider>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import type { SortableGridRenderItem } from 'react-native-sortables'
|
||||
import type { MoneyCategoryItem } from '@/types/money/category'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Platform, Text, Vibration, View } from 'react-native'
|
||||
|
||||
import Animated, { useAnimatedRef } from 'react-native-reanimated'
|
||||
import Sortable, { DragStartParams } from 'react-native-sortables'
|
||||
import { AButton, ASegmentedTabs, LucideIcon } from '@/components/common/ao'
|
||||
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
|
||||
export default function Example() {
|
||||
const [selectedTab, setSelectedTab] = useState(0)
|
||||
const moneyCategoryStore = useMoneyCategoryStore()
|
||||
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
|
||||
|
||||
const [categories, setCategories] = useState<MoneyCategoryItem[]>([])
|
||||
|
||||
const [draggingId, setDraggingId] = useState<string>('')
|
||||
const [draggingDeleteZone, setDraggingDeleteZone] = useState<boolean>(false)
|
||||
const [deleteTimer, setDeleteTimer] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setCategories(selectedTab === 0 ? moneyCategoryStore.getExpenseCategories() : moneyCategoryStore.getIncomeCategories())
|
||||
}, [selectedTab])
|
||||
|
||||
const renderItem = useCallback<SortableGridRenderItem<MoneyCategoryItem>>(
|
||||
({ item }) => (
|
||||
<View
|
||||
className="flex flex-col items-center justify-center"
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 28,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F3F4F6',
|
||||
}}
|
||||
>
|
||||
<MoneyCategoryIcon size={24} name={item.icon} />
|
||||
</View>
|
||||
<Text style={{
|
||||
marginBottom: 4,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<CustomHeader
|
||||
title="类别设置"
|
||||
/>
|
||||
|
||||
<View className="flex-1">
|
||||
<View className="pt-2 mb-4 px-3 flex flex-row items-center justify-between">
|
||||
<ASegmentedTabs
|
||||
defaultIndex={selectedTab}
|
||||
onChange={(index) => {
|
||||
setSelectedTab(index)
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: '收入',
|
||||
},
|
||||
{
|
||||
label: '支出',
|
||||
},
|
||||
]}
|
||||
style={{
|
||||
width: 140,
|
||||
}}
|
||||
/>
|
||||
|
||||
<View className="flex flex-row gap-2">
|
||||
<AButton
|
||||
className="rounded-full"
|
||||
leftIcon={{
|
||||
icon: 'Plus',
|
||||
className: 'mr-0',
|
||||
color: '#fff',
|
||||
clickable: false,
|
||||
size: 18,
|
||||
}}
|
||||
>
|
||||
添加分类
|
||||
</AButton>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
|
||||
<View className="flex-1 px-3">
|
||||
<Sortable.MultiZoneProvider>
|
||||
<Sortable.PortalProvider enabled>
|
||||
<Animated.ScrollView
|
||||
ref={scrollableRef}
|
||||
>
|
||||
<Sortable.Grid
|
||||
columnGap={10}
|
||||
rowGap={10}
|
||||
columns={4}
|
||||
data={categories}
|
||||
renderItem={renderItem}
|
||||
scrollableRef={scrollableRef}
|
||||
autoScrollActivationOffset={200}
|
||||
showDropIndicator
|
||||
onDragStart={(data) => {
|
||||
Vibration.vibrate(50)
|
||||
setDraggingId(data.key)
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
setDraggingId('')
|
||||
}}
|
||||
/>
|
||||
</Animated.ScrollView>
|
||||
|
||||
<Sortable.BaseZone
|
||||
onItemEnter={() => {
|
||||
if (!draggingId)
|
||||
return
|
||||
|
||||
setDraggingDeleteZone(true)
|
||||
Vibration.vibrate(50)
|
||||
|
||||
// 设置 1 秒后删除
|
||||
const timer = setTimeout(() => {
|
||||
setCategories(prev => prev.filter(c => c.id !== Number(draggingId)))
|
||||
setDraggingId('')
|
||||
setDraggingDeleteZone(false)
|
||||
setDeleteTimer(null)
|
||||
}, 1000)
|
||||
|
||||
setDeleteTimer(timer as unknown as number)
|
||||
}}
|
||||
onItemLeave={() => {
|
||||
setDraggingDeleteZone(false)
|
||||
if (deleteTimer) {
|
||||
clearTimeout(deleteTimer)
|
||||
setDeleteTimer(null)
|
||||
}
|
||||
}}
|
||||
onItemDrop={() => {
|
||||
setDraggingDeleteZone(false)
|
||||
if (deleteTimer) {
|
||||
clearTimeout(deleteTimer)
|
||||
setDeleteTimer(null)
|
||||
}
|
||||
}}
|
||||
minActivationDistance={40}
|
||||
>
|
||||
<View
|
||||
className={`flex flex-row items-center justify-center h-12 rounded-md gap-2 ${
|
||||
draggingDeleteZone ? 'bg-red-600' : 'bg-red-400'
|
||||
}`}
|
||||
>
|
||||
<LucideIcon icon="Trash" size={18} color="#fff" />
|
||||
<Text className="text-base text-white ">
|
||||
拖到这里删除
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
</Sortable.BaseZone>
|
||||
|
||||
<View className="flex flex-row mb-8 mt-4 w-full gap-4">
|
||||
<AButton
|
||||
className="flex-1"
|
||||
variant="outline"
|
||||
leftIcon={{
|
||||
icon: 'RefreshCcw',
|
||||
className: 'mr-0',
|
||||
color: '#333',
|
||||
clickable: false,
|
||||
size: 18,
|
||||
}}
|
||||
>
|
||||
恢复
|
||||
</AButton>
|
||||
|
||||
<AButton
|
||||
className="flex-1"
|
||||
leftIcon={{
|
||||
icon: 'Save',
|
||||
className: 'mr-0',
|
||||
color: '#fff',
|
||||
clickable: false,
|
||||
size: 18,
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</AButton>
|
||||
</View>
|
||||
|
||||
</Sortable.PortalProvider>
|
||||
</Sortable.MultiZoneProvider>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -148,7 +148,12 @@ export default function MoenyRecordEditor() {
|
||||
<PagerTabs
|
||||
selectedIndex={selectedCategory?.type}
|
||||
left={(
|
||||
<TouchableOpacity className="w-[30%] flex items-center px-4 flex-row">
|
||||
<TouchableOpacity
|
||||
className="w-[30%] flex items-center px-4 flex-row"
|
||||
onPress={() => {
|
||||
router.navigate('/money/category/setting')
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="LayoutList" />
|
||||
<Text className="ml-1">管理</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -7,6 +7,7 @@ import FormItem from './FormItem'
|
||||
import Input from './Input'
|
||||
import LucideIcon from './LucideIcon'
|
||||
import Modal from './Modal'
|
||||
import SegmentedTabs from './SegmentedTabs'
|
||||
|
||||
export {
|
||||
Button as AButton,
|
||||
@@ -19,5 +20,6 @@ export {
|
||||
FormRef as AFormRef,
|
||||
Input as AInput,
|
||||
Modal as AModal,
|
||||
SegmentedTabs as ASegmentedTabs,
|
||||
LucideIcon,
|
||||
}
|
||||
@@ -70,14 +70,17 @@ export default function CustomHeader(props: CustomHeaderProps) {
|
||||
style,
|
||||
]}
|
||||
>
|
||||
|
||||
{
|
||||
left || (
|
||||
<View className="w-10 h-full justify-center">
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<LucideIcon icon="ArrowLeft" size={24} clickable={false} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
{/* 左侧 */}
|
||||
<View className="w-10 h-full justify-center">
|
||||
{left || (
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<LucideIcon icon="ArrowLeft" size={24} clickable={false} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 标题 */}
|
||||
<View className="flex-1 items-center justify-center">
|
||||
@@ -86,15 +89,15 @@ export default function CustomHeader(props: CustomHeaderProps) {
|
||||
: title}
|
||||
</View>
|
||||
|
||||
{/* 右侧 */}
|
||||
<View className="w-10 h-full items-center justify-center">
|
||||
{right || (
|
||||
{right || (
|
||||
<View className="w-10 h-full items-center justify-center">
|
||||
<Image
|
||||
source={require('@/static/images/logo.png')}
|
||||
style={{ width: 22, height: 22 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { LayoutChangeEvent } from 'react-native'
|
||||
import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Animated,
|
||||
LayoutChangeEvent as RNLayoutChangeEvent,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import PagerView from 'react-native-pager-view'
|
||||
|
||||
interface TabsContextType {
|
||||
pagerRef: React.RefObject<PagerView>
|
||||
scrollX: Animated.Value
|
||||
index: number
|
||||
setIndex: (i: number) => void
|
||||
}
|
||||
|
||||
const TabsContext = createContext<TabsContextType | null>(null)
|
||||
|
||||
interface TabsProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function TabsProvider({ children }: TabsProviderProps) {
|
||||
const pagerRef = useRef<PagerView>(null!)
|
||||
const scrollX = useRef(new Animated.Value(0)).current
|
||||
const [index, setIndex] = useState(0)
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ pagerRef, scrollX, index, setIndex }}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------- Tabs (Header) ----------------
|
||||
interface TabsProps {
|
||||
tabs: string[]
|
||||
leftContent?: ReactNode
|
||||
rightContent?: ReactNode
|
||||
}
|
||||
|
||||
function useSafeTabsContext(): TabsContextType {
|
||||
const ctx = useContext(TabsContext)
|
||||
if (!ctx) {
|
||||
const pagerRef = useRef<PagerView>(null!)
|
||||
const scrollX = useRef(new Animated.Value(0)).current
|
||||
const [index, setIndex] = useState(0)
|
||||
return { pagerRef, scrollX, index, setIndex }
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
TabsProvider.Tabs = function Tabs({ tabs, leftContent, rightContent }: TabsProps) {
|
||||
const ctx = useSafeTabsContext()
|
||||
|
||||
const tabLayouts = useRef<{ x: number, width: number }[]>([])
|
||||
const scrollRef = useRef<ScrollView>(null)
|
||||
|
||||
const onTabLayout = (e: LayoutChangeEvent, i: number) => {
|
||||
const { x, width } = e.nativeEvent.layout
|
||||
tabLayouts.current[i] = { x, width }
|
||||
}
|
||||
|
||||
const measured = tabLayouts.current.length === tabs.length
|
||||
|
||||
const underlineX = measured
|
||||
? ctx.scrollX.interpolate({
|
||||
inputRange: tabs.map((_, i) => i),
|
||||
outputRange: tabLayouts.current.map(t => t.x),
|
||||
})
|
||||
: 0
|
||||
|
||||
const underlineWidth = measured
|
||||
? ctx.scrollX.interpolate({
|
||||
inputRange: tabs.map((_, i) => i),
|
||||
outputRange: tabLayouts.current.map(t => t.width),
|
||||
})
|
||||
: 0
|
||||
|
||||
// 自动滚动 ScrollView 保持选中 Tab 可见
|
||||
useEffect(() => {
|
||||
if (!measured || !scrollRef.current)
|
||||
return
|
||||
const layout = tabLayouts.current[ctx.index]
|
||||
if (!layout)
|
||||
return
|
||||
scrollRef.current.scrollTo({ x: Math.max(layout.x - 50, 0), animated: true })
|
||||
}, [ctx.index, measured])
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', borderBottomWidth: 1, borderColor: '#eee' }}>
|
||||
{leftContent && <View>{leftContent}</View>}
|
||||
|
||||
<ScrollView
|
||||
ref={scrollRef}
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ flexDirection: 'row', position: 'relative' }}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
onPress={() => {
|
||||
ctx.pagerRef.current?.setPage(i)
|
||||
ctx.setIndex(i)
|
||||
}}
|
||||
onLayout={e => onTabLayout(e, i)}
|
||||
style={{ paddingHorizontal: 20, paddingVertical: 12 }}
|
||||
>
|
||||
<Text style={{ color: ctx.index === i ? '#000' : '#999' }}>{tab}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
{measured && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.underline,
|
||||
{ width: underlineWidth, transform: [{ translateX: underlineX }] },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{rightContent && <View>{rightContent}</View>}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------- Content ----------------
|
||||
TabsProvider.Content = function Content({ tabs }: { tabs: string[] }) {
|
||||
const ctx = useSafeTabsContext()
|
||||
|
||||
return (
|
||||
<PagerView
|
||||
ref={ctx.pagerRef}
|
||||
style={{ flex: 1 }}
|
||||
initialPage={0}
|
||||
onPageScroll={e =>
|
||||
ctx.scrollX.setValue(e.nativeEvent.position + e.nativeEvent.offset)}
|
||||
onPageSelected={e => ctx.setIndex(e.nativeEvent.position)}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<View key={i} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 20 }}>
|
||||
{tab}
|
||||
{' '}
|
||||
页面内容
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</PagerView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
underline: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
height: 2,
|
||||
backgroundColor: '#007AFF',
|
||||
},
|
||||
})
|
||||
@@ -30,8 +30,8 @@ function isWhiteListed(url: string) {
|
||||
}
|
||||
|
||||
const alovaInstance = createAlova({
|
||||
// baseURL: 'http://192.168.5.6:8085/app',
|
||||
baseURL: 'http://192.168.1.136:8085/app',
|
||||
baseURL: 'http://192.168.5.6:8086/app',
|
||||
// baseURL: 'http://192.168.1.136:8085/app',
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
timeout: 5000,
|
||||
cacheFor: null,
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface MoneyRecordBase {
|
||||
remark: string
|
||||
record_time: string
|
||||
create_time: string
|
||||
update_time: string
|
||||
update_time?: string
|
||||
}
|
||||
|
||||
// 查询条件(请求参数)
|
||||
|
||||
Reference in new issue
Block a user