234 lines
7.6 KiB
TypeScript
234 lines
7.6 KiB
TypeScript
import React, { useState, useEffect, useRef } from 'react'
|
|
import { View, Text, ScrollView } from '@tarojs/components'
|
|
import { CommonPopup } from '../index'
|
|
import styles from './index.module.scss'
|
|
|
|
export interface TimePickerProps {
|
|
visible: boolean
|
|
onClose: () => void
|
|
onConfirm: (year: number, month: number) => void
|
|
defaultYear?: number
|
|
defaultMonth?: number
|
|
minYear?: number
|
|
maxYear?: number
|
|
}
|
|
|
|
const TimePicker: React.FC<TimePickerProps> = ({
|
|
visible,
|
|
onClose,
|
|
onConfirm,
|
|
defaultYear = new Date().getFullYear(),
|
|
defaultMonth = new Date().getMonth() + 1,
|
|
minYear = 2020,
|
|
maxYear = 2030
|
|
}) => {
|
|
const [selectedYear, setSelectedYear] = useState(defaultYear)
|
|
const [selectedMonth, setSelectedMonth] = useState(defaultMonth)
|
|
const [yearScrollTop, setYearScrollTop] = useState(0)
|
|
const [monthScrollTop, setMonthScrollTop] = useState(0)
|
|
|
|
const yearScrollRef = useRef<any>(null)
|
|
const monthScrollRef = useRef<any>(null)
|
|
|
|
// 计算当前选项在数组中的索引
|
|
const getYearIndex = (year: number) => year - minYear
|
|
const getMonthIndex = (month: number) => month - 1
|
|
|
|
// 生成选择器的选项数据
|
|
const yearOptions = Array.from({ length: maxYear - minYear + 1 }, (_, index) => ({
|
|
text: `${minYear + index}年`,
|
|
value: minYear + index
|
|
}))
|
|
|
|
const monthOptions = Array.from({ length: 12 }, (_, index) => ({
|
|
text: `${index + 1}月`,
|
|
value: index + 1
|
|
}))
|
|
|
|
// 计算滚动位置 - 确保每次只显示一个选项
|
|
const calculateScrollTop = (index: number) => {
|
|
const itemHeight = 48 // 每个选项的高度
|
|
const containerHeight = 216 // 容器高度
|
|
const centerOffset = (containerHeight - itemHeight) / 2
|
|
return index * itemHeight - centerOffset
|
|
}
|
|
|
|
// 获取当前可见的选项数量
|
|
const getVisibleItemCount = () => {
|
|
const containerHeight = 216
|
|
const itemHeight = 48
|
|
return Math.floor(containerHeight / itemHeight)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
setSelectedYear(defaultYear)
|
|
setSelectedMonth(defaultMonth)
|
|
|
|
// 设置初始滚动位置
|
|
const yearScrollTop = calculateScrollTop(getYearIndex(defaultYear))
|
|
const monthScrollTop = calculateScrollTop(getMonthIndex(defaultMonth))
|
|
setYearScrollTop(yearScrollTop)
|
|
setMonthScrollTop(monthScrollTop)
|
|
}
|
|
}, [visible, defaultYear, defaultMonth])
|
|
|
|
// 处理年份滚动
|
|
const handleYearScroll = (event: any) => {
|
|
const scrollTop = event.detail.scrollTop
|
|
const itemHeight = 48
|
|
const containerHeight = 216
|
|
const centerOffset = (containerHeight - itemHeight) / 2
|
|
|
|
// 计算当前选中的年份索引
|
|
const currentIndex = Math.round((scrollTop + centerOffset) / itemHeight)
|
|
const clampedIndex = Math.max(0, Math.min(currentIndex, yearOptions.length - 1))
|
|
const newYear = minYear + clampedIndex
|
|
|
|
if (newYear !== selectedYear) {
|
|
setSelectedYear(newYear)
|
|
}
|
|
}
|
|
|
|
// 处理年份滚动结束,自动对齐
|
|
const handleYearScrollEnd = () => {
|
|
const yearIndex = getYearIndex(selectedYear)
|
|
const alignedScrollTop = calculateScrollTop(yearIndex)
|
|
// 使用setTimeout确保滚动动画完成后再对齐
|
|
setTimeout(() => {
|
|
setYearScrollTop(alignedScrollTop)
|
|
}, 100)
|
|
}
|
|
|
|
// 处理月份滚动
|
|
const handleMonthScroll = (event: any) => {
|
|
const scrollTop = event.detail.scrollTop
|
|
const itemHeight = 48
|
|
const containerHeight = 216
|
|
const centerOffset = (containerHeight - itemHeight) / 2
|
|
|
|
// 计算当前选中的月份索引
|
|
const currentIndex = Math.round((scrollTop + centerOffset) / itemHeight)
|
|
const clampedIndex = Math.max(0, Math.min(currentIndex, monthOptions.length - 1))
|
|
const newMonth = clampedIndex + 1
|
|
|
|
if (newMonth !== selectedMonth) {
|
|
setSelectedMonth(newMonth)
|
|
}
|
|
}
|
|
|
|
// 处理月份滚动结束,自动对齐
|
|
const handleMonthScrollEnd = () => {
|
|
const monthIndex = getMonthIndex(selectedMonth)
|
|
const alignedScrollTop = calculateScrollTop(monthIndex)
|
|
// 使用setTimeout确保滚动动画完成后再对齐
|
|
setTimeout(() => {
|
|
setMonthScrollTop(alignedScrollTop)
|
|
}, 100)
|
|
}
|
|
|
|
const handleConfirm = () => {
|
|
onConfirm(selectedYear, selectedMonth)
|
|
onClose()
|
|
}
|
|
|
|
if (!visible) return null
|
|
|
|
return (
|
|
<CommonPopup
|
|
visible={visible}
|
|
onClose={onClose}
|
|
onConfirm={handleConfirm}
|
|
showHeader={false}
|
|
hideFooter={false}
|
|
cancelText="返回"
|
|
confirmText="完成"
|
|
position="bottom"
|
|
round={true}
|
|
className={styles['time-picker-popup']}
|
|
>
|
|
{/* 拖拽手柄 */}
|
|
<View className={styles['popup-handle']} />
|
|
|
|
{/* 时间选择器 */}
|
|
<View className={styles['picker-container']}>
|
|
{/* 自定义多列选择器 */}
|
|
<View className={styles['picker-wrapper']}>
|
|
<View className={styles['custom-picker']}>
|
|
{/* 选中项指示器 */}
|
|
<View className={styles['picker-indicator']} />
|
|
|
|
{/* 年份列 */}
|
|
<View className={styles['picker-column']}>
|
|
<ScrollView
|
|
ref={yearScrollRef}
|
|
scrollY
|
|
scrollTop={yearScrollTop}
|
|
onScroll={handleYearScroll}
|
|
onTouchEnd={handleYearScrollEnd}
|
|
onScrollToLower={handleYearScrollEnd}
|
|
onScrollToUpper={handleYearScrollEnd}
|
|
className={styles['picker-scroll']}
|
|
scrollWithAnimation={true}
|
|
enhanced={true}
|
|
showScrollbar={false}
|
|
bounces={false}
|
|
fastDeceleration={true}
|
|
>
|
|
<View className={styles['picker-padding']} />
|
|
{yearOptions.map((option, index) => (
|
|
<View
|
|
key={option.value}
|
|
className={`${styles['picker-item']} ${
|
|
option.value === selectedYear ? styles['picker-item-active'] : ''
|
|
}`}
|
|
data-value={option.value}
|
|
>
|
|
<Text className={styles['picker-item-text']}>{option.text}</Text>
|
|
</View>
|
|
))}
|
|
<View className={styles['picker-padding']} />
|
|
</ScrollView>
|
|
</View>
|
|
|
|
{/* 月份列 */}
|
|
<View className={styles['picker-column']}>
|
|
<ScrollView
|
|
ref={monthScrollRef}
|
|
scrollY
|
|
scrollTop={monthScrollTop}
|
|
onScroll={handleMonthScroll}
|
|
onTouchEnd={handleMonthScrollEnd}
|
|
onScrollToLower={handleMonthScrollEnd}
|
|
onScrollToUpper={handleMonthScrollEnd}
|
|
className={styles['picker-scroll']}
|
|
scrollWithAnimation={true}
|
|
enhanced={true}
|
|
showScrollbar={false}
|
|
bounces={false}
|
|
fastDeceleration={true}
|
|
>
|
|
<View className={styles['picker-padding']} />
|
|
{monthOptions.map((option, index) => (
|
|
<View
|
|
key={option.value}
|
|
className={`${styles['picker-item']} ${
|
|
option.value === selectedMonth ? styles['picker-item-active'] : ''
|
|
}`}
|
|
data-value={option.value}
|
|
>
|
|
<Text className={styles['picker-item-text']}>{option.text}</Text>
|
|
</View>
|
|
))}
|
|
<View className={styles['picker-padding']} />
|
|
</ScrollView>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</CommonPopup>
|
|
)
|
|
}
|
|
|
|
export default TimePicker
|