This commit is contained in:
oneao committed 2026-02-05 22:46:50 +08:00
1 parent 2b76078a77
commit a030d1c92e
5 files changed
+449 -168

No files matched your search

+49 -17
View File
@@ -1,28 +1,60 @@
import SegmentedControl from '@react-native-segmented-control/segmented-control'
import React, { useState } from 'react' import React, { useState } from 'react'
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { Text, View } from 'react-native'
import { AButton, ADateMorePicker, ATab } from '@/components/common/ao' import { AButton, ADateMorePicker } from '@/components/common/ao'
export default function DemoDatePickerUI() { 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 ( return (
<View className="flex-1"> <View className="flex-1 p-4 bg-white">
<AButton {/* 当前选择结果展示 */}
onPress={() => { <View className="mb-4 p-3 rounded bg-gray-100">
setShow(true) <Text className="text-gray-600 mb-1">当前选择:</Text>
}}
className="mt-20" {result.startTime || result.endTime
> ? (
测试 <>
<Text>
开始时间:
{result.startTime || '-'}
</Text>
<Text>
结束时间:
{result.endTime || '-'}
</Text>
</>
)
: (
<Text className="text-gray-400">未选择</Text>
)}
</View>
{/* 打开选择器 */}
<AButton onPress={() => setVisible(true)}>
打开日期选择器
</AButton> </AButton>
{/* 日期选择器 */}
<ADateMorePicker <ADateMorePicker
visible={show} visible={visible}
onClose={() => { onClose={() => setVisible(false)}
setShow(false) mode={mode}
startDate={result.startTime}
endDate={result.endTime}
onConfirm={(value) => {
setMode(value.mode)
// setResult(value)
setVisible(false)
}} }}
> />
</ADateMorePicker>
</View> </View>
) )
} }
@@ -177,7 +177,6 @@ export default function Analysis() {
? ( ? (
<VictoryPie <VictoryPie
height={250} height={250}
animate={{ duration: 1000 }}
data={pieRecords} data={pieRecords}
theme={VictoryTheme.material} theme={VictoryTheme.material}
/> />
@@ -1,104 +1,292 @@
import SegmentedControl from '@react-native-segmented-control/segmented-control' 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 { useEffect, useState } from 'react'
import { ScrollView, Text, View } from 'react-native' import { ScrollView, Text, View } from 'react-native'
import { getCurrentDate, toDate } from '@/utils/time'
import Button from './Button' import Button from './Button'
import Drawer from './Drawer' import Drawer from './Drawer'
import LucideIcon from './LucideIcon'
import Tab from './Tab' import Tab from './Tab'
export type DatePickerMode = 'year' | 'month' | 'date' | 'time' | 'datetime' /** ================= 类型 ================= */
export type DateFormatType
= | 'YYYY' export type PickerMode
| 'YYYY-MM' = | 'unlimited'
| 'YYYY-MM-DD' | 'date'
| 'HH:mm:ss' | 'month'
| 'YYYY-MM-DD HH:mm:ss' | 'year'
| 'time'
| 'datetime'
| 'range'
type PickerModeInternal = PickerMode | 'custom'
type TabKey = 'custom' | PickerMode
type DatePickerMode = 'date' | 'month' | 'year' | 'time' | 'datetime'
interface DateMorePickerProps { interface DateMorePickerProps {
mode?: PickerMode
startDate?: Date | string startDate?: Date | string
endDate?: Date | string endDate?: Date | string
visible?: boolean visible?: boolean
onClose?: () => void onClose?: () => void
onConfirm?: (value: string) => void onConfirm?: (value: {
mode?: DatePickerMode mode: PickerMode
format?: DateFormatType startTime: string
endTime: string
}) => void
onChange?: (mode: PickerMode) => void
} }
export default function DateMorePicker({ export default function DateMorePicker({
mode,
startDate,
endDate,
visible, visible,
onClose, onClose,
onConfirm,
onChange,
}: DateMorePickerProps) { }: DateMorePickerProps) {
const [localVisible, setLocalVisible] = useState(visible || false) const [localVisible, setLocalVisible] = useState(false)
const [index, setIndex] = useState(0)
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(() => { useEffect(() => {
setLocalVisible(!!visible) if (visible) {
resetInternalState()
setLocalVisible(true)
}
}, [visible]) }, [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) setLocalVisible(false)
onClose?.() 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> const formatMap: Record<PickerMode, string> = {
<Text className="text-lg mb-1">选择模式</Text> unlimited: '',
<SegmentedControl range: 'YYYY-MM-DD',
values={['日', '月', '年', '时间', '日期']} date: 'YYYY-MM-DD',
selectedIndex={index} month: 'YYYY-MM',
style={{ width: '100%', borderRadius: 2 }} year: 'YYYY',
sliderStyle={{ borderRadius: 2 }} time: 'HH:mm:ss',
onChange={(e: any) => datetime: 'YYYY-MM-DD HH:mm:ss',
setIndex(e.nativeEvent.selectedSegmentIndex)} }
/>
</View> 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> </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 <ScrollView
style={{ flex: 1 }} style={{ flex: 1 }}
showsVerticalScrollIndicator={false} contentContainerStyle={{
showsHorizontalScrollIndicator={false} width: '100%',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 10,
}}
> >
<DateRangePicker {internalMode === 'unlimited' && (
isModal={false} <View className="items-center">
showHeader={false} <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> </ScrollView>
<View {/* 按钮 */}
className="flex-row gap-3 pt-2" <View className="flex-row gap-3 pt-2">
style={{ flexShrink: 0 }}
>
<Button className="flex-1" variant="outline" onPress={handleClose}> <Button className="flex-1" variant="outline" onPress={handleClose}>
取消 取消
</Button> </Button>
<Button className="flex-1"> <Button className="flex-1" onPress={handleConfirm}>
确认 确认
</Button> </Button>
</View> </View>
@@ -3,8 +3,6 @@ import type {
} from 'react-native' } from 'react-native'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { import {
NativeScrollEvent,
NativeSyntheticEvent,
ScrollView, ScrollView,
Text, Text,
TouchableWithoutFeedback, TouchableWithoutFeedback,
+149 -85
View File
@@ -1,97 +1,161 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>日期选择 Picker Demo</title>
<title>可编辑表格示例</title> <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: sans-serif;
padding: 20px; padding: 20px;
} }
table { .category-tabs {
border-collapse: collapse; display: flex;
width: 100%; gap: 10px;
table-layout: fixed; margin-bottom: 10px;
} flex-wrap: wrap;
th, td { }
border: 1px solid #ccc; .category-tab {
padding: 5px; padding: 6px 12px;
text-align: center; border: 1px solid #ccc;
} border-radius: 4px;
input[type="text"], input[type="date"] { cursor: pointer;
width: 90%; transition: all 0.2s;
box-sizing: border-box; }
} .category-tab.active {
input[type="checkbox"] { background-color: #409eff;
transform: scale(1.2); color: white;
} border-color: #409eff;
button { }
margin-top: 10px; .shortcut-options {
padding: 5px 10px; display: flex;
} gap: 10px;
</style> margin-top: 10px;
flex-wrap: wrap;
}
.shortcut-option {
padding: 6px 12px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.shortcut-option.active {
background-color: #67c23a;
color: white;
border-color: #67c23a;
}
</style>
</head> </head>
<body> <body>
<div id="app">
<h2>日期选择器 Demo</h2>
<!-- 分类标签 -->
<div class="category-tabs">
<div
v-for="cat in categories"
:key="cat"
class="category-tab"
:class="{ active: selectedCategory === cat }"
@click="selectCategory(cat)"
>
{{ cat }}
</div>
</div>
<!-- 日期选择器 -->
<el-date-picker
v-model="dateValue"
:type="pickerType"
placeholder="请选择日期"
:value-format="valueFormat"
style="width: 100%;"
></el-date-picker>
<h2>可编辑表格示例</h2> <!-- 快捷选项 -->
<div class="shortcut-options">
<div
v-for="item in shortcuts"
:key="item.label"
class="shortcut-option"
:class="{ active: activeShortcut === item.label }"
@click="applyShortcut(item)"
>
{{ item.label }}
</div>
</div>
<table id="editableTable"> <div style="margin-top: 20px;">
<thead> <strong>选择的值:</strong> {{ dateValue }}
<tr> </div>
<!-- 生成 20 个列标题 --> </div>
<script>
for(let i=1; i<=20; i++){ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
document.write(`<th>列${i}</th>`); <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
} <script>
</script> const { createApp, ref } = Vue
</tr> const { ElDatePicker } = ElementPlus
</thead>
<tbody> createApp({
<!-- 初始 20 行 --> setup() {
<script> const categories = ['日', '月', '年', '时间', '日期', '范围', '不限']
function createRow(rowIndex){ const selectedCategory = ref('日')
let row = '<tr>'; const dateValue = ref(null)
for(let i=1;i<=20;i++){ const activeShortcut = ref(null)
if(i % 3 === 1){ // 文本输入
row += `<td><input type="text" placeholder="文本${rowIndex}-${i}"></td>`; const shortcuts = [
} else if(i % 3 === 2){ // 日期选择 { label: '今天', getValue: () => new Date() },
row += `<td><input type="date"></td>`; { label: '本月', getValue: () => {
} else { // 复选框 const now = new Date()
row += `<td><input type="checkbox"></td>`; return [new Date(now.getFullYear(), now.getMonth(), 1), new Date(now.getFullYear(), now.getMonth() + 1, 0)]
}},
{ label: '本年', getValue: () => {
const now = new Date()
return [new Date(now.getFullYear(), 0, 1), new Date(now.getFullYear(), 11, 31)]
}},
]
const pickerType = Vue.computed(() => {
switch(selectedCategory.value){
case '日': return 'date'
case '月': return 'month'
case '年': return 'year'
case '时间': return 'time'
case '日期': return 'date'
case '范围': return 'daterange'
case '不限': return 'date'
default: return 'date'
} }
})
const valueFormat = Vue.computed(() => {
switch(selectedCategory.value){
case '日': return 'yyyy-MM-dd'
case '月': return 'yyyy-MM'
case '年': return 'yyyy'
case '时间': return 'HH:mm:ss'
case '日期': return 'yyyy-MM-dd'
case '范围': return 'yyyy-MM-dd'
case '不限': return 'yyyy-MM-dd'
default: return 'yyyy-MM-dd'
}
})
function selectCategory(cat) {
selectedCategory.value = cat
dateValue.value = null
activeShortcut.value = null
} }
row += '</tr>';
return row; function applyShortcut(item) {
activeShortcut.value = item.label
dateValue.value = item.getValue()
}
return { categories, selectedCategory, dateValue, pickerType, valueFormat, shortcuts, activeShortcut, selectCategory, applyShortcut }
} }
}).use(ElementPlus).mount('#app')
for(let r=1;r<=20;r++){ </script>
document.write(createRow(r));
}
</script>
</tbody>
</table>
<button onclick="addRow()">新增行</button>
<script>
function addRow(){
const tbody = document.querySelector('#editableTable tbody');
const rowCount = tbody.rows.length + 1;
let newRow = '<tr>';
for(let i=1;i<=20;i++){
if(i % 3 === 1){
newRow += `<td><input type="text" placeholder="文本${rowCount}-${i}"></td>`;
} else if(i % 3 === 2){
newRow += `<td><input type="date"></td>`;
} else {
newRow += `<td><input type="checkbox"></td>`;
}
}
newRow += '</tr>';
tbody.insertAdjacentHTML('beforeend', newRow);
}
</script>
</body> </body>
</html> </html>