diff --git a/code/allapp/allapp-rn/package.json b/code/allapp/allapp-rn/package.json
index 3edec494..a5985f19 100644
--- a/code/allapp/allapp-rn/package.json
+++ b/code/allapp/allapp-rn/package.json
@@ -18,6 +18,7 @@
"@legendapp/list": "^2.0.19",
"@legendapp/motion": "2.4.0",
"@react-native-async-storage/async-storage": "2.2.0",
+ "@react-native-segmented-control/segmented-control": "^2.5.7",
"@react-navigation/drawer": "^7.5.0",
"@yz1311/react-native-wheel-picker": "^0.4.5",
"alova": "^3.3.4",
diff --git a/code/allapp/allapp-rn/pnpm-lock.yaml b/code/allapp/allapp-rn/pnpm-lock.yaml
index 5bef9632..26be92a5 100644
--- a/code/allapp/allapp-rn/pnpm-lock.yaml
+++ b/code/allapp/allapp-rn/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
'@react-native-async-storage/async-storage':
specifier: 2.2.0
version: 2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
+ '@react-native-segmented-control/segmented-control':
+ specifier: ^2.5.7
+ version: 2.5.7(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
'@react-navigation/drawer':
specifier: ^7.5.0
version: 7.7.9(@react-navigation/native@7.1.19(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -1702,6 +1705,12 @@ packages:
peerDependencies:
react-native: ^0.0.0-0 || >=0.65 <1.0
+ '@react-native-segmented-control/segmented-control@2.5.7':
+ resolution: {integrity: sha512-l84YeVX8xAU3lvOJSvV4nK/NbGhIm2gBfveYolwaoCbRp+/SLXtc6mYrQmM9ScXNwU14mnzjQTpTHWl5YPnkzQ==}
+ peerDependencies:
+ react: '>=16.0'
+ react-native: '>=0.62'
+
'@react-native/assets-registry@0.81.5':
resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==}
engines: {node: '>= 20.19.4'}
@@ -8278,6 +8287,11 @@ snapshots:
merge-options: 3.0.4
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
+ '@react-native-segmented-control/segmented-control@2.5.7(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
+
'@react-native/assets-registry@0.81.5': {}
'@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.28.5)':
diff --git a/code/allapp/allapp-rn/src/app/(tabs)/love.tsx b/code/allapp/allapp-rn/src/app/(tabs)/love.tsx
index b972112c..def56adc 100644
--- a/code/allapp/allapp-rn/src/app/(tabs)/love.tsx
+++ b/code/allapp/allapp-rn/src/app/(tabs)/love.tsx
@@ -1,40 +1,85 @@
-import { DateRangePicker } from '@yz1311/react-native-wheel-picker'
-import React from 'react'
-import { Button, ButtonText } from '@/components/ui/button'
+import SegmentedControl from '@react-native-segmented-control/segmented-control'
+import React, { useState } from 'react'
+import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import { ATab } from '@/components/common/ao'
+
+export default function DemoDatePickerUI() {
+ const [index, setIndex] = useState(0)
-export default function Example() {
- const [showDrawer, setShowDrawer] = React.useState(false)
return (
- <>
-
+
+ {/* 快捷选项栏 */}
+
+
+
- {
- }}
- onPickerCancel={() => {
- }}
- onPickerConfirm={(date) => {
- setShowDrawer(false)
- }}
+ {/* SegmentedControl */}
+ setIndex(event.nativeEvent.selectedSegmentIndex)}
+ style={styles.segmentedControl}
+ sliderStyle={styles.sliderStyle}
/>
- >
+
+ {/* 日期选择器占位 */}
+
+ 这里显示日期选择器
+
+
)
}
+
+const styles = StyleSheet.create({
+ quickOption: {
+ paddingVertical: 6,
+ paddingHorizontal: 12,
+ backgroundColor: '#fff',
+ borderRadius: 6,
+ borderWidth: 1,
+ borderColor: '#ccc',
+ marginRight: 8,
+ height: 32,
+ },
+ quickOptionText: {
+ fontSize: 14,
+ color: '#333',
+ },
+ segmentedControl: {
+ borderRadius: 0,
+ height: 36,
+ backgroundColor: '#e5e7eb',
+ marginBottom: 12,
+ },
+ sliderStyle: {
+ borderRadius: 0,
+ backgroundColor: '#2196f3',
+ },
+ datePickerPlaceholder: {
+ height: 200,
+ backgroundColor: '#fff',
+ borderRadius: 6,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+})
diff --git a/code/allapp/allapp-rn/src/app/(tabs)/money/components/CategorySheet.tsx b/code/allapp/allapp-rn/src/app/(tabs)/money/components/CategorySheet.tsx
index 1c5c6234..83368594 100644
--- a/code/allapp/allapp-rn/src/app/(tabs)/money/components/CategorySheet.tsx
+++ b/code/allapp/allapp-rn/src/app/(tabs)/money/components/CategorySheet.tsx
@@ -1,5 +1,7 @@
import type { MoneyCategoryItem } from '@/types/money/category'
-import { useCallback, useEffect, useMemo, useRef } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { AInput, LucideIcon } from '@/components/common/ao'
+import EmptyState from '@/components/common/EmptyStatus'
import {
Actionsheet,
ActionsheetBackdrop,
@@ -34,6 +36,7 @@ export default function CategorySheet({
}: CategorySheetProps) {
const listRef = useRef(null)
const hasAutoScrolled = useRef(false)
+ const [searchText, setSearchText] = useState('')
// 记住目标滚动位置
const pendingScrollRef = useRef<{
@@ -128,17 +131,45 @@ export default function CategorySheet({
}
}, [open, value, sections])
+ const filteredSections = useMemo(() => {
+ if (!searchText.trim()) {
+ return sections
+ }
+
+ const lower = searchText.trim().toLowerCase()
+
+ return sections.map(section => ({
+ ...section,
+ data: section.data.filter(item =>
+ item.name.toLowerCase().includes(lower),
+ ),
+ })).filter(section => section.data.length > 0)
+ }, [sections, searchText])
+
+ function handleClose() {
+ onClose?.()
+ setSearchText('')
+ }
+
return (
-
+
-
+
+ }
+ />
+
+
{s.title}
{' '}
(
@@ -187,6 +224,10 @@ export default function CategorySheet({
)
}}
/>
+
+ {filteredSections.length === 0 && (
+
+ )}
)
diff --git a/code/allapp/allapp-rn/src/app/money/analysis/index.tsx b/code/allapp/allapp-rn/src/app/money/analysis/index.tsx
index 81434f0d..45932fdd 100644
--- a/code/allapp/allapp-rn/src/app/money/analysis/index.tsx
+++ b/code/allapp/allapp-rn/src/app/money/analysis/index.tsx
@@ -2,7 +2,6 @@ import type { AnalysisMoneyAmount } from '@/types/money/analysis'
import dayjs from 'dayjs'
import { useEffect, useState } from 'react'
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'
-import Svg from 'react-native-svg'
import { VictoryChart, VictoryLabel, VictoryLine, VictoryPie, VictoryTheme, VictoryTooltip } from 'victory-native'
import { ADatePicker, ADateRangePicker, ASegmentedTabs, LucideIcon } from '@/components/common/ao'
import EmptyState from '@/components/common/EmptyStatus'
@@ -10,6 +9,7 @@ import CustomHeader from '@/components/custom/Header'
import PagerTabs from '@/components/custom/PagerTabs'
import { analysisMoneyLineApi, analysisMoneyPieApi } from '@/request/api/money/analysis'
import { MoneyCategoryEnum } from '@/types/money/category'
+import { add } from '@/utils/math'
import { getCurrentYear, getCurrentYearMonth, getEndOfMonth, getStartOfMonth } from '@/utils/time'
interface AnalysisContentProps {
@@ -55,10 +55,6 @@ export default function Analysis() {
periodType = 1
}
else {
- console.log(selectedStartDate.isSame(selectedEndDate, 'day'))
- console.log(selectedStartDate.isSame(selectedEndDate, 'month'))
- console.log(selectedStartDate.isSame(selectedEndDate, 'year'))
-
if (selectedStartDate.isSame(selectedEndDate, 'month')) {
periodType = 0
start = selectedStartDate.startOf('month').format('YYYY-MM-DD')
@@ -98,6 +94,10 @@ export default function Analysis() {
}
})
}
+ const totalAmount: number = pieRecords.reduce(
+ (sum, item) => Number(add(sum, Number(item.y) || 0)),
+ 0,
+ )
useEffect(() => {
fetchChartsData()
@@ -190,16 +190,24 @@ export default function Analysis() {
- 分类详细
+
+ 分类详细
+
+ 总:
+ {totalAmount}
+ {' '}
+ 元
+
+
{
pieRecords.length > 0
? (
-
+
{pieRecords.map((item, index) => (
{item.x}
diff --git a/code/allapp/allapp-rn/src/components/common/ao/DateMorePicker.tsx b/code/allapp/allapp-rn/src/components/common/ao/DateMorePicker.tsx
new file mode 100644
index 00000000..8cb4ff42
--- /dev/null
+++ b/code/allapp/allapp-rn/src/components/common/ao/DateMorePicker.tsx
@@ -0,0 +1,3 @@
+export default function ADateMorePicker() {
+
+}
diff --git a/code/allapp/allapp-rn/src/components/common/ao/DatePicker.tsx b/code/allapp/allapp-rn/src/components/common/ao/DatePicker.tsx
index fa59a91d..a5e9f17f 100644
--- a/code/allapp/allapp-rn/src/components/common/ao/DatePicker.tsx
+++ b/code/allapp/allapp-rn/src/components/common/ao/DatePicker.tsx
@@ -1,9 +1,12 @@
import type { IDatePickerProps } from '@yz1311/react-native-wheel-picker'
+import SegmentedControl from '@react-native-segmented-control/segmented-control'
import { DatePicker } from '@yz1311/react-native-wheel-picker'
import dayjs from 'dayjs'
import React, { useEffect, useRef, useState } from 'react'
-import { Animated, Modal, StyleSheet, TouchableOpacity } from 'react-native'
+import { Animated, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
+import { AButton, ATab } from '@/components/common/ao'
import { toDate } from '@/utils/time'
+
// 定义 mode 类型
export type DatePickerMode = 'year' | 'month' | 'date' | 'time' | 'datetime'
@@ -32,13 +35,14 @@ export default function ADatePicker({
show = false,
onClose,
onConfirm,
- mode = 'date',
format,
...restProps
}: ADatePickerProps) {
const fadeAnim = useRef(new Animated.Value(0)).current
const slideAnim = useRef(new Animated.Value(300)).current
const [visible, setVisible] = useState(show)
+ const [index, setIndex] = useState(0)
+ const [mode, setMode] = useState('month')
// 使用传入 format 或默认 format
const finalFormat = format || defaultFormatMap[mode]
@@ -80,6 +84,58 @@ export default function ADatePicker({
+
+
+ 快速选择
+ {
+ setMode('datetime')
+ }}
+ />
+
+
+
+ 选择模式
+ setIndex(event.nativeEvent.selectedSegmentIndex)}
+ />
+
+
+
+
{
+ console.log(value)
+ }}
/>
+
+
+ 取消
+ 确认
+
)
diff --git a/code/allapp/allapp-rn/src/components/common/ao/SegmentedTabs.tsx b/code/allapp/allapp-rn/src/components/common/ao/SegmentedTabs.tsx
index 462207d4..cdc925d0 100644
--- a/code/allapp/allapp-rn/src/components/common/ao/SegmentedTabs.tsx
+++ b/code/allapp/allapp-rn/src/components/common/ao/SegmentedTabs.tsx
@@ -39,7 +39,7 @@ interface SegmentedTabsProps {
width?: number
}
-export default function SegmentedTabs({
+export default function SegmentedTabs({
tabs,
value,
defaultIndex = 0,
diff --git a/code/allapp/allapp-rn/src/components/common/ao/Tab.tsx b/code/allapp/allapp-rn/src/components/common/ao/Tab.tsx
index 4016eefa..3b4b6474 100644
--- a/code/allapp/allapp-rn/src/components/common/ao/Tab.tsx
+++ b/code/allapp/allapp-rn/src/components/common/ao/Tab.tsx
@@ -1,5 +1,5 @@
import React, { useMemo, useRef, useState } from 'react'
-import { Pressable, Text, View } from 'react-native'
+import { Pressable, ScrollView, Text, View } from 'react-native'
import PagerView from 'react-native-pager-view'
interface TabItem {
@@ -12,18 +12,12 @@ interface TabProps {
data?: TabItem[]
defaultKey?: TabItem['key']
onChange?: (key: TabItem['key']) => void
- tabBarClass?: string
- tabItemClass?: string
- activeTabItemClass?: string
}
export default function Tab({
data,
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) {
if (!data || data.length === 0)
return null
@@ -72,22 +66,34 @@ export default function Tab({
return (
{/* tab bar */}
-
+
{data.map((item) => {
const isActive = item.key === selectedKey
return (
handlePress(item.key)}
- className={isActive ? activeTabItemClass : tabItemClass}
+ className={
+ isActive
+ ? 'items-center justify-center rounded-md px-4 py-2 bg-gray-800'
+ : 'items-center justify-center rounded-md px-4 py-2 bg-gray-200'
+ }
>
+
{item.label}
)
})}
-
+
{hasContent && (