u
This commit is contained in:
1 parent
2b76078a77
commit
a030d1c92e
5 files changed
+449
-168
No files matched your search
@@ -1,28 +1,60 @@
|
||||
import SegmentedControl from '@react-native-segmented-control/segmented-control'
|
||||
import React, { useState } from 'react'
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
||||
import { AButton, ADateMorePicker, ATab } from '@/components/common/ao'
|
||||
import { Text, View } from 'react-native'
|
||||
import { AButton, ADateMorePicker } from '@/components/common/ao'
|
||||
|
||||
export default function DemoDatePickerUI() {
|
||||
const [show, setShow] = useState(false)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [mode, setMode] = useState('')
|
||||
const [result, setResult] = useState<{
|
||||
startTime: string
|
||||
endTime: string
|
||||
}>({
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
})
|
||||
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<AButton
|
||||
onPress={() => {
|
||||
setShow(true)
|
||||
}}
|
||||
className="mt-20"
|
||||
>
|
||||
测试
|
||||
<View className="flex-1 p-4 bg-white">
|
||||
{/* 当前选择结果展示 */}
|
||||
<View className="mb-4 p-3 rounded bg-gray-100">
|
||||
<Text className="text-gray-600 mb-1">当前选择:</Text>
|
||||
|
||||
{result.startTime || result.endTime
|
||||
? (
|
||||
<>
|
||||
<Text>
|
||||
开始时间:
|
||||
{result.startTime || '-'}
|
||||
</Text>
|
||||
<Text>
|
||||
结束时间:
|
||||
{result.endTime || '-'}
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<Text className="text-gray-400">未选择</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 打开选择器 */}
|
||||
<AButton onPress={() => setVisible(true)}>
|
||||
打开日期选择器
|
||||
</AButton>
|
||||
|
||||
{/* 日期选择器 */}
|
||||
<ADateMorePicker
|
||||
visible={show}
|
||||
onClose={() => {
|
||||
setShow(false)
|
||||
visible={visible}
|
||||
onClose={() => setVisible(false)}
|
||||
mode={mode}
|
||||
startDate={result.startTime}
|
||||
endDate={result.endTime}
|
||||
onConfirm={(value) => {
|
||||
setMode(value.mode)
|
||||
// setResult(value)
|
||||
setVisible(false)
|
||||
}}
|
||||
>
|
||||
</ADateMorePicker>
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -177,7 +177,6 @@ export default function Analysis() {
|
||||
? (
|
||||
<VictoryPie
|
||||
height={250}
|
||||
animate={{ duration: 1000 }}
|
||||
data={pieRecords}
|
||||
theme={VictoryTheme.material}
|
||||
/>
|
||||
|
||||
@@ -1,104 +1,292 @@
|
||||
import SegmentedControl from '@react-native-segmented-control/segmented-control'
|
||||
import { DateRangePicker } from '@yz1311/react-native-wheel-picker'
|
||||
import { DatePicker, DateRangePicker } from '@yz1311/react-native-wheel-picker'
|
||||
import dayjs from 'dayjs'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ScrollView, Text, View } from 'react-native'
|
||||
import { getCurrentDate, toDate } from '@/utils/time'
|
||||
import Button from './Button'
|
||||
import Drawer from './Drawer'
|
||||
import LucideIcon from './LucideIcon'
|
||||
import Tab from './Tab'
|
||||
|
||||
export type DatePickerMode = 'year' | 'month' | 'date' | 'time' | 'datetime'
|
||||
export type DateFormatType
|
||||
= | 'YYYY'
|
||||
| 'YYYY-MM'
|
||||
| 'YYYY-MM-DD'
|
||||
| 'HH:mm:ss'
|
||||
| 'YYYY-MM-DD HH:mm:ss'
|
||||
/** ================= 类型 ================= */
|
||||
|
||||
export type PickerMode
|
||||
= | 'unlimited'
|
||||
| 'date'
|
||||
| 'month'
|
||||
| 'year'
|
||||
| 'time'
|
||||
| 'datetime'
|
||||
| 'range'
|
||||
|
||||
type PickerModeInternal = PickerMode | 'custom'
|
||||
type TabKey = 'custom' | PickerMode
|
||||
type DatePickerMode = 'date' | 'month' | 'year' | 'time' | 'datetime'
|
||||
|
||||
interface DateMorePickerProps {
|
||||
mode?: PickerMode
|
||||
startDate?: Date | string
|
||||
endDate?: Date | string
|
||||
visible?: boolean
|
||||
onClose?: () => void
|
||||
onConfirm?: (value: string) => void
|
||||
mode?: DatePickerMode
|
||||
format?: DateFormatType
|
||||
onConfirm?: (value: {
|
||||
mode: PickerMode
|
||||
startTime: string
|
||||
endTime: string
|
||||
}) => void
|
||||
onChange?: (mode: PickerMode) => void
|
||||
}
|
||||
|
||||
export default function DateMorePicker({
|
||||
mode,
|
||||
startDate,
|
||||
endDate,
|
||||
visible,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onChange,
|
||||
}: DateMorePickerProps) {
|
||||
const [localVisible, setLocalVisible] = useState(visible || false)
|
||||
const [index, setIndex] = useState(0)
|
||||
const [localVisible, setLocalVisible] = useState(false)
|
||||
|
||||
const coreModes: DatePickerMode[] = ['date', 'month', 'year', 'time', 'datetime']
|
||||
const coreLabels = ['日', '月', '年', '时间', '日期']
|
||||
|
||||
/** ================= 内部编辑态 ================= */
|
||||
|
||||
const [internalMode, setInternalMode] = useState<PickerMode>('date')
|
||||
const [internalStart, setInternalStart] = useState<Date | string>(getCurrentDate())
|
||||
const [internalEnd, setInternalEnd] = useState<Date | string>(getCurrentDate())
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('custom')
|
||||
|
||||
/** ================= 重置编辑态(关键) ================= */
|
||||
|
||||
const resetInternalState = () => {
|
||||
setInternalMode(mode || 'date')
|
||||
setInternalStart(startDate ?? getCurrentDate())
|
||||
setInternalEnd(endDate ?? getCurrentDate())
|
||||
setActiveTab('custom')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLocalVisible(!!visible)
|
||||
if (visible) {
|
||||
resetInternalState()
|
||||
setLocalVisible(true)
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
function handleClose() {
|
||||
/** ================= 快速选择逻辑(修复时间归零) ================= */
|
||||
|
||||
const handleModeChange = (next: PickerModeInternal) => {
|
||||
const now = dayjs()
|
||||
|
||||
switch (next) {
|
||||
case 'custom':
|
||||
setInternalMode('date')
|
||||
break
|
||||
|
||||
case 'unlimited':
|
||||
setInternalStart('')
|
||||
setInternalEnd('')
|
||||
setInternalMode('unlimited')
|
||||
break
|
||||
|
||||
case 'range': {
|
||||
const base = now.startOf('day')
|
||||
setInternalStart(base.toDate())
|
||||
setInternalEnd(base.toDate())
|
||||
setInternalMode('range')
|
||||
break
|
||||
}
|
||||
|
||||
case 'date': {
|
||||
const base = now.startOf('day')
|
||||
setInternalStart(base.toDate())
|
||||
setInternalEnd(base.toDate())
|
||||
setInternalMode('date')
|
||||
break
|
||||
}
|
||||
|
||||
case 'month': {
|
||||
const base = now.startOf('month')
|
||||
setInternalStart(base.toDate())
|
||||
setInternalEnd(base.toDate())
|
||||
setInternalMode('month')
|
||||
break
|
||||
}
|
||||
|
||||
case 'year': {
|
||||
const base = now.startOf('year')
|
||||
setInternalStart(base.toDate())
|
||||
setInternalEnd(base.toDate())
|
||||
setInternalMode('year')
|
||||
break
|
||||
}
|
||||
|
||||
case 'time': {
|
||||
const base = now.second(0)
|
||||
setInternalStart(base.toDate())
|
||||
setInternalEnd(base.toDate())
|
||||
setInternalMode('time')
|
||||
break
|
||||
}
|
||||
|
||||
case 'datetime': {
|
||||
setInternalStart(now.toDate())
|
||||
setInternalEnd(now.toDate())
|
||||
setInternalMode('datetime')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onChange?.(next === 'custom' ? 'date' : next)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setLocalVisible(false)
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
visible={localVisible}
|
||||
hideModal={handleClose}
|
||||
showHeader={false}
|
||||
height="60%"
|
||||
>
|
||||
<View className="flex-1 p-3">
|
||||
<View
|
||||
className="gap-2"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<View>
|
||||
<Text className="text-lg mb-1">快速选择</Text>
|
||||
<Tab
|
||||
data={[
|
||||
{ label: '自定义', key: '自定义' },
|
||||
{ label: '不限', key: '不限' },
|
||||
{ label: '今天', key: '今天' },
|
||||
{ label: '本月', key: '本月' },
|
||||
{ label: '本年', key: '本年' },
|
||||
{ label: '范围', key: '范围' },
|
||||
]}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
</View>
|
||||
/** ================= 确认(提交) ================= */
|
||||
|
||||
<View>
|
||||
<Text className="text-lg mb-1">选择模式</Text>
|
||||
<SegmentedControl
|
||||
values={['日', '月', '年', '时间', '日期']}
|
||||
selectedIndex={index}
|
||||
style={{ width: '100%', borderRadius: 2 }}
|
||||
sliderStyle={{ borderRadius: 2 }}
|
||||
onChange={(e: any) =>
|
||||
setIndex(e.nativeEvent.selectedSegmentIndex)}
|
||||
/>
|
||||
</View>
|
||||
const formatMap: Record<PickerMode, string> = {
|
||||
unlimited: '',
|
||||
range: 'YYYY-MM-DD',
|
||||
date: 'YYYY-MM-DD',
|
||||
month: 'YYYY-MM',
|
||||
year: 'YYYY',
|
||||
time: 'HH:mm:ss',
|
||||
datetime: 'YYYY-MM-DD HH:mm:ss',
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!onConfirm)
|
||||
return
|
||||
|
||||
let startStr = ''
|
||||
let endStr = ''
|
||||
|
||||
if (internalMode === 'unlimited') {
|
||||
startStr = ''
|
||||
endStr = ''
|
||||
}
|
||||
else if (internalMode === 'range') {
|
||||
startStr = dayjs(internalStart).format(formatMap.range)
|
||||
endStr = dayjs(internalEnd).format(formatMap.range)
|
||||
}
|
||||
else {
|
||||
const fmt = formatMap[internalMode]
|
||||
startStr = dayjs(internalStart).format(fmt)
|
||||
endStr = startStr
|
||||
}
|
||||
|
||||
onConfirm({
|
||||
mode: internalMode,
|
||||
startTime: startStr,
|
||||
endTime: endStr,
|
||||
})
|
||||
|
||||
handleClose()
|
||||
}
|
||||
|
||||
/** ================= UI ================= */
|
||||
|
||||
return (
|
||||
<Drawer visible={localVisible} hideModal={handleClose} showHeader={false} height="50%">
|
||||
<View className="flex-1 p-3">
|
||||
|
||||
{/* 快速选择 */}
|
||||
<View className="mb-2">
|
||||
<Text className="text-lg mb-1">快速选择</Text>
|
||||
<Tab
|
||||
defaultKey={activeTab}
|
||||
data={[
|
||||
{ label: '自定义', key: 'custom' },
|
||||
{ label: '不限', key: 'unlimited' },
|
||||
{ label: '今天', key: 'date' },
|
||||
{ label: '本月', key: 'month' },
|
||||
{ label: '本年', key: 'year' },
|
||||
{ label: '范围', key: 'range' },
|
||||
]}
|
||||
onChange={(key) => {
|
||||
setActiveTab(key as TabKey)
|
||||
handleModeChange(key as PickerModeInternal)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 模式选择 */}
|
||||
{!['unlimited', 'range'].includes(internalMode) && (
|
||||
<View className="mb-2">
|
||||
<Text className="text-lg mb-1">选择模式</Text>
|
||||
<SegmentedControl
|
||||
values={coreLabels}
|
||||
selectedIndex={coreModes.indexOf(internalMode as DatePickerMode)}
|
||||
style={{ width: '100%', borderRadius: 2 }}
|
||||
sliderStyle={{ borderRadius: 2 }}
|
||||
onChange={(e: any) => {
|
||||
const next = coreModes[e.nativeEvent.selectedSegmentIndex]
|
||||
setInternalMode(next)
|
||||
setInternalStart(dayjs().toDate())
|
||||
setActiveTab('custom')
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Picker */}
|
||||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 10,
|
||||
}}
|
||||
>
|
||||
<DateRangePicker
|
||||
isModal={false}
|
||||
showHeader={false}
|
||||
/>
|
||||
{internalMode === 'unlimited' && (
|
||||
<View className="items-center">
|
||||
<LucideIcon icon="Clock" size={38} color="#555" />
|
||||
<Text className="mt-2 text-lg">不限时间</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{internalMode === 'range' && (
|
||||
<DateRangePicker
|
||||
isModal={false}
|
||||
showHeader={false}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
onValueChange={(start, end) => {
|
||||
setInternalStart(start)
|
||||
setInternalEnd(end)
|
||||
if (activeTab !== 'range')
|
||||
setActiveTab('custom')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{coreModes.includes(internalMode as DatePickerMode) && (
|
||||
<DatePicker
|
||||
key={internalMode}
|
||||
isModal={false}
|
||||
showHeader={false}
|
||||
mode={internalMode as DatePickerMode}
|
||||
date={toDate(internalStart)}
|
||||
style={{ height: '100%' }}
|
||||
onDateChange={(value) => {
|
||||
setInternalStart(value)
|
||||
setActiveTab('custom')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<View
|
||||
className="flex-row gap-3 pt-2"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{/* 按钮 */}
|
||||
<View className="flex-row gap-3 pt-2">
|
||||
<Button className="flex-1" variant="outline" onPress={handleClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button className="flex-1">
|
||||
<Button className="flex-1" onPress={handleConfirm}>
|
||||
确认
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -3,8 +3,6 @@ import type {
|
||||
} from 'react-native'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
|
||||
Reference in new issue
Block a user