修改日历组件下滑位置

This commit is contained in:
筱野
2025-11-14 15:50:00 +08:00
parent 12d597aec2
commit b13979c4ab
9 changed files with 336 additions and 34 deletions

View File

@@ -68,7 +68,7 @@ const CommonPopup: React.FC<CommonPopupProps> = ({
// 只允许向下拖动,限制最大拖动距离 // 只允许向下拖动,限制最大拖动距离
if (deltaY > 0) { if (deltaY > 0) {
setDragOffset(Math.min(deltaY, 200)) setDragOffset(Math.min(deltaY, 100))
} }
} }
@@ -78,7 +78,7 @@ const CommonPopup: React.FC<CommonPopupProps> = ({
setIsDragging(false) setIsDragging(false)
// 如果拖动距离超过阈值,关闭弹窗 // 如果拖动距离超过阈值,关闭弹窗
if (dragOffset > 100) { if (dragOffset > 50) {
onClose() onClose()
} }
@@ -100,7 +100,10 @@ const CommonPopup: React.FC<CommonPopupProps> = ({
}} }}
> >
{enableDragToClose && ( {enableDragToClose && (
<View className={styles['common-popup__drag-handle-container']}> <View className={styles['common-popup__drag-handle-container']}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}>
<View <View
className={styles['common-popup__drag-handle']} className={styles['common-popup__drag-handle']}
style={{ style={{
@@ -108,9 +111,7 @@ const CommonPopup: React.FC<CommonPopupProps> = ({
opacity: isDragging ? 0.8 : 1, opacity: isDragging ? 0.8 : 1,
transition: isDragging ? 'none' : 'all 0.3s ease-out' transition: isDragging ? 'none' : 'all 0.3s ease-out'
}} }}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
/> />
</View> </View>
)} )}

View File

@@ -12,16 +12,20 @@
position: absolute; position: absolute;
top: 6px; top: 6px;
left: 50%; left: 50%;
width: 90px;
height: 30px;
z-index: 10;
cursor: pointer;
transition: background-color 0.2s ease;
display: flex;
justify-content: center;
align-items: flex-start;
&::before{
content: '';
width: 32px; width: 32px;
height: 4px; height: 4px;
background-color: rgba(22, 24, 35, 0.2); background-color: rgba(22, 24, 35, 0.2);
border-radius: 2px; border-radius: 2px;
z-index: 10;
cursor: pointer;
transition: background-color 0.2s ease;
&:active {
background-color: #9ca3af;
} }
} }

View File

@@ -0,0 +1,132 @@
import React, { useState, useEffect, useRef } from "react";
import CommonPopup from "@/components/CommonPopup";
import { View } from "@tarojs/components";
import CalendarUI, {
CalendarUIRef,
} from "@/components/Picker/CalendarUI/CalendarUI";
import dayjs from "dayjs";
import styles from "../CalendarDialog/index.module.scss";
export interface DayDialogProps {
value?: Date | Date[];
searchType?: "single" | "range" | "multiple";
onChange?: (date: string | string[]) => void;
visible: boolean;
onClose: () => void;
title?: React.ReactNode;
}
const DayDialog: React.FC<DayDialogProps> = ({
visible,
searchType,
onClose,
title,
value,
onChange,
}) => {
const [selected, setSelected] = useState<Date | Date[]>(value || new Date());
const [selectedBackup, setSelectedBackup] = useState<Date[]>(
Array.isArray(value) ? [...(value as Date[])] : [value as Date]
);
const calendarRef = useRef<CalendarUIRef>(null);
const [type, setType] = useState<"year" | "month" | "time">("year");
const [pendingJump, setPendingJump] = useState<{
year: number;
month: number;
} | null>(null);
const handleConfirm = () => {
console.log(selected, 'selectedselected');
const finalDate = dayjs(selected as Date).format("YYYY-MM-DD");
if (onChange){
onChange(finalDate)
}
onClose();
};
const dialogClose = () => {
setType("year");
onClose();
}
const handleChange = (d: Date | Date[]) => {
if (searchType === "range") {
if (Array.isArray(d)) {
if (d.length === 2) {
return;
} else if (d.length === 1) {
if (selectedBackup.length === 0 || selectedBackup.length === 2) {
setSelected([...d]);
setSelectedBackup([...d]);
} else {
setSelected(
[...selectedBackup, d[0]].sort(
(a, b) => a.getTime() - b.getTime()
)
);
setSelectedBackup([]);
}
}
return;
}
}
if (Array.isArray(d)) {
setSelected(d[0]);
} else {
setSelected(d);
}
};
const onHeaderClick = (date: Date) => {
console.log("onHeaderClick", date);
setSelected(date);
setType("month");
};
const getConfirmText = () => {
return "完成"
};
const onCancel = () => {
onClose();
};
useEffect(() => {
if (visible && value) {
setSelected(value || new Date());
}
}, [value, visible]);
useEffect(() => {
if (type === "year" && pendingJump && calendarRef.current) {
calendarRef.current.jumpTo(pendingJump.year, pendingJump.month);
setPendingJump(null);
}
}, [type, pendingJump]);
return (
<CommonPopup
visible={visible}
onClose={dialogClose}
onCancel={onCancel}
showHeader={!!title}
title={title}
hideFooter={false}
cancelText="取消"
confirmText={getConfirmText()}
onConfirm={handleConfirm}
className={styles["calendar-popup"]}
position="bottom"
round
zIndex={1000}
>
<View className={styles["calendar-container"]}>
<CalendarUI
ref={calendarRef}
type={searchType}
value={selected}
onChange={handleChange}
showQuickActions={false}
onHeaderClick={onHeaderClick}
/>
</View>
</CommonPopup>
);
};
export default DayDialog;

View File

@@ -0,0 +1,85 @@
import React, { useState, useEffect, useRef } from "react";
import CommonPopup from "@/components/CommonPopup";
import { View } from "@tarojs/components";
import { PickerCommonRef, PickerCommon } from "@/components/Picker";
import dayjs from "dayjs";
import styles from "../CalendarDialog/index.module.scss";
export interface HourDialogProps {
value?: Date | Date[];
searchType?: "single" | "range" | "multiple";
onChange?: (any) => void;
visible: boolean;
onClose: () => void;
title?: React.ReactNode;
}
const HourDialog: React.FC<HourDialogProps> = ({
visible,
onClose,
title,
value,
onChange,
}) => {
const [selectedTime, setSelectTime] = useState<(string | number)[]>([8,0]);
const hourMinutePickerRef = useRef<PickerCommonRef>(null);
const handleConfirm = () => {
const value = hourMinutePickerRef.current?.getValue();
if (onChange) {
onChange(value);
}
dialogClose();
};
const dialogClose = () => {
onClose();
}
const getConfirmText = () => {
return "完成";
};
const onCancel = () => {
onClose();
};
useEffect(() => {
if (visible && value) {
// setSelectedHour(value ? dayjs(value as Date).hour() : 8);
// setSelectedMinute(value ? dayjs(value as Date).minute() : 0);
const hour = value ? dayjs(value as Date).hour() : 8;
const minute = value ? dayjs(value as Date).minute() : 0
setSelectTime([hour, minute])
}
}, [value, visible]);
return (
<CommonPopup
visible={visible}
onClose={dialogClose}
onCancel={onCancel}
showHeader={!!title}
title={title}
hideFooter={false}
cancelText="取消"
confirmText={getConfirmText()}
onConfirm={handleConfirm}
className={styles["calendar-popup"]}
position="bottom"
round
zIndex={1000}
>
<View className={styles["calendar-container"]}>
<PickerCommon
ref={hourMinutePickerRef}
type="hour"
value={selectedTime}
/>
</View>
</CommonPopup>
);
};
export default HourDialog;

View File

@@ -28,7 +28,7 @@ const CustomPicker = ({
// 当外部 defaultValue 变化时,同步更新内部状态 // 当外部 defaultValue 变化时,同步更新内部状态
useEffect(() => { useEffect(() => {
setCurrentValue(defaultValue) handleValuesChange(defaultValue);
}, [defaultValue]) }, [defaultValue])
const confirmPicker = ( const confirmPicker = (
@@ -45,14 +45,28 @@ const CustomPicker = ({
} }
} }
const changePicker = useCallback((options: any[], values: any, columnIndex: number) => { const handleValuesChange = (valuesList) => {
// 更新内部状态 const isSame =
setCurrentValue(values) Array.isArray(valuesList) &&
Array.isArray(currentValue) &&
valuesList.length === currentValue.length &&
valuesList.every((v: any, idx: number) => v === currentValue[idx])
if (onChange) { console.log(isSame,valuesList, 'isSameisSameisSameisSame');
if (!isSame) {
setCurrentValue(valuesList)
}
return isSame;
}
const changePicker = useCallback((options: any[], values: any, columnIndex: number) => {
// 值相同则不触发更新,避免受控/非受控同步造成的回流循环
const isSame = handleValuesChange(values)
if (onChange && !isSame) {
onChange(options, values, columnIndex) onChange(options, values, columnIndex)
} }
}, [onChange]) }, [onChange, currentValue])
return ( return (
<> <>

View File

@@ -5,3 +5,5 @@ export type { PickerCommonRef } from './PickerCommon'
export { default as CalendarUI } from './CalendarUI/CalendarUI' export { default as CalendarUI } from './CalendarUI/CalendarUI'
export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard' export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard'
export { default as CityPicker } from './CityPicker' export { default as CityPicker } from './CityPicker'
export { default as DayDialog } from './DayDialog';
export { default as HourDialog } from './HourDialog';

View File

@@ -9,7 +9,7 @@
margin-top: 8px; margin-top: 8px;
.additional-input { .additional-input {
width: 100%; width: 100%;
height: 46px; min-height: 46px;
font-size: 14px; font-size: 14px;
color: #333; color: #333;
background: transparent; background: transparent;

View File

@@ -72,7 +72,7 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
placeholderClass='textarea-placeholder' placeholderClass='textarea-placeholder'
onInput={handleTextChange} onInput={handleTextChange}
maxlength={maxLength} maxlength={maxLength}
autoHeight={false} autoHeight={true}
onFocus={onFocus} onFocus={onFocus}
onBlur={onBlur} onBlur={onBlur}
/> />

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import { View, Text, } from '@tarojs/components' import { View, Text, } from '@tarojs/components'
import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils' import { getDate, getTime, getDateStr, getEndTime } from '@/utils/timeUtils'
import { DialogCalendarCard } from '@/components/index' import { DialogCalendarCard } from '@/components/index'
import { DayDialog, HourDialog } from '@/components/Picker/index';
import './TimeSelector.scss' import './TimeSelector.scss'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@@ -35,6 +36,9 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
const [currentTimeValue, setCurrentTimeValue] = useState<Date>(new Date()) const [currentTimeValue, setCurrentTimeValue] = useState<Date>(new Date())
const [currentTimeType, setCurrentTimeType] = useState<'start' | 'end'>('start') const [currentTimeType, setCurrentTimeType] = useState<'start' | 'end'>('start')
const [showEndTime, setShowEndTime] = useState(false) const [showEndTime, setShowEndTime] = useState(false)
const [visibleDay, setVisibleDay] = useState(false);
const [visibleHour, setVisibleHour] = useState(false);
const handleConfirm = (date: Date) => { const handleConfirm = (date: Date) => {
console.log('选择的日期:', date) console.log('选择的日期:', date)
const start_time = currentTimeType === 'start' ? getDateStr(date) : value.start_time; const start_time = currentTimeType === 'start' ? getDateStr(date) : value.start_time;
@@ -46,12 +50,56 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
} }
if (onChange) onChange({start_time, end_time}) if (onChange) onChange({start_time, end_time})
} }
const onChangeDay = (years:any) => {
if (onChange){
if (currentTimeType === 'start') {
const hour = dayjs(value.start_time).format('HH:mm')
onChange({start_time: `${years} ${hour}`, end_time: value.end_time})
} else {
const hour = dayjs(value.end_time).format('HH:mm')
onChange({start_time: value.start_time, end_time: `${years} ${hour}`})
}
}
}
const onChangeHour = (dates) => {
const [hour, minute] = dates;
if (onChange){
if (currentTimeType === 'start') {
const year = dayjs(value.start_time).format('YYYY-MM-DD')
onChange({start_time: `${year} ${hour}:${minute}`, end_time: value.end_time})
} else {
const year = dayjs(value.end_time).format('YYYY-MM-DD')
onChange({start_time: value.start_time, end_time: `${year} ${hour}:${minute}`})
}
}
}
const openPicker = (type: 'start' | 'end') => { const openPicker = (type: 'start' | 'end') => {
setCurrentTimeValue(type === 'start' ? parseDate(value.start_time) : parseDate(value.end_time)) setCurrentTimeValue(type === 'start' ? parseDate(value.start_time) : parseDate(value.end_time))
setCurrentTimeType(type) setCurrentTimeType(type)
setVisible(true) setVisible(true)
} }
const openDay = (type: 'start' | 'end') => {
setCurrentTimeValue(type === 'start' ? parseDate(value.start_time) : parseDate(value.end_time))
setCurrentTimeType(type)
setVisibleDay(true)
}
const openHour = (type: 'start' | 'end') => {
setCurrentTimeValue(type === 'start' ? parseDate(value.start_time) : parseDate(value.end_time))
setVisibleHour(true)
setCurrentTimeType(type)
}
const endHandle = () => {
if (showEndTime) {
openHour('end')
} else {
openPicker('end')
}
}
useEffect(() => { useEffect(() => {
if (value.start_time && value.end_time) { if (value.start_time && value.end_time) {
const start_time = dayjs(value.start_time).format('YYYY-MM-DD') const start_time = dayjs(value.start_time).format('YYYY-MM-DD')
@@ -71,14 +119,14 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-label'> <View className='time-label'>
<View className='dot'></View> <View className='dot'></View>
</View> </View>
<View className='time-content' onClick={() => openPicker('start')}> <View className='time-content' >
<Text className='time-label'></Text> <Text className='time-label'></Text>
<view className='time-text-wrapper'> <view className='time-text-wrapper'>
{value.start_time && (<> {value.start_time && (<>
<Text className='time-text'>{getDate(value.start_time)}</Text> <Text className='time-text' onClick={() => openDay('start')}>{getDate(value.start_time)}</Text>
<Text className='time-text time-am'>{getTime(value.start_time)}</Text> <Text className='time-text time-am' onClick={() => openHour('start')}>{getTime(value.start_time)}</Text>
</>)} </>)}
{!value.start_time && (<Text className='time-text'></Text>)} {!value.start_time && (<Text className='time-text' onClick={() => openPicker('start')}></Text>)}
</view> </view>
</View> </View>
</View> </View>
@@ -88,12 +136,12 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-label'> <View className='time-label'>
<View className='dot hollow'></View> <View className='dot hollow'></View>
</View> </View>
<View className='time-content' onClick={() => openPicker('end')}> <View className='time-content' >
<Text className='time-label'></Text> <Text className='time-label'></Text>
<view className='time-text-wrapper'> <view className='time-text-wrapper'>
{value.end_time && (<>{showEndTime && (<Text className='time-text'>{getDate(value.end_time)}</Text>)} {value.end_time && (<>{showEndTime && (<Text className='time-text' onClick={() => openDay('end')}>{getDate(value.end_time)}</Text>)}
<Text className='time-text time-am'>{getTime(value.end_time)}</Text></>)} <Text className='time-text time-am' onClick={() => endHandle()}>{getTime(value.end_time)}</Text></>)}
{!value.end_time && (<Text className='time-text'></Text>)} {!value.end_time && (<Text className='time-text' onClick={() => openPicker('end')}></Text>)}
</view> </view>
</View> </View>
</View> </View>
@@ -106,6 +154,22 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
onClose={() => setVisible(false)} onClose={() => setVisible(false)}
/> />
} }
{
visibleDay && <DayDialog
visible={visibleDay}
value={currentTimeValue}
onChange={onChangeDay}
onClose={() => setVisibleDay(false)}
/>
}
{
visibleHour && <HourDialog
visible={visibleHour}
value={currentTimeValue}
onChange={onChangeHour}
onClose={() => setVisibleHour(false)}
/>
}
</View> </View>
) )
} }