Files
mini-programs/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx
2026-02-07 22:15:14 +08:00

238 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef } from "react";
import CommonPopup from "@/components/CommonPopup";
import { View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import CalendarUI, {
CalendarUIRef,
} from "@/components/Picker/CalendarUI/CalendarUI";
import { PickerCommon, PickerCommonRef } from "@/components/Picker";
import dayjs from "dayjs";
import styles from "./index.module.scss";
export interface DialogCalendarCardProps {
value?: Date | Date[];
searchType?: "single" | "range" | "multiple";
onChange?: (date: Date | Date[]) => void;
visible: boolean;
onClose: () => void;
title?: React.ReactNode;
}
const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
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 [current, setCurrent] = useState<Date>(new Date());
const [delta, setDelta] = useState(0);
const calendarRef = useRef<CalendarUIRef>(null);
const [type, setType] = useState<"year" | "month" | "time">("year");
const [selectedHour, setSelectedHour] = useState(8);
const [selectedMinute, setSelectedMinute] = useState(0);
const pickerRef = useRef<PickerCommonRef>(null);
const hourMinutePickerRef = useRef<PickerCommonRef>(null);
const [pendingJump, setPendingJump] = useState<{
year: number;
month: number;
} | null>(null);
const handleConfirm = () => {
if (type === "year") {
if (searchType === "range") {
if (onChange) onChange(selected);
onClose();
return;
}
if (!selected) {
Taro.showToast({
title: '请选择日期',
icon: "none",
});
return;
}
// 年份选择完成后,进入月份选择
setType("time");
} else if (type === "month") {
// 月份选择完成后,进入时间选择
const value = pickerRef.current?.getValue();
if (value) {
const year = value[0] as number;
const month = value[1] as number;
setPendingJump({ year, month });
setType("year");
if (searchType === "range") {
calculateMonthDifference(
current,
new Date(year, month - 1, 1)
);
return;
}
setSelected(new Date(year, month - 1, 1));
}
} else if (type === "time") {
// 时间选择完成后调用onNext回调
const value = hourMinutePickerRef.current?.getValue();
if (value) {
const hour = value[0] as number;
const minute = value[1] as number;
setSelectedHour(hour);
setSelectedMinute(minute);
const hours = hour.toString().padStart(2, "0");
const minutes = minute.toString().padStart(2, "0");
const finalDate = new Date(
dayjs(selected as Date).format("YYYY-MM-DD") +
" " +
hours +
":" +
minutes
);
if (onChange) onChange(finalDate);
}
onClose();
}
};
const dialogClose = () => {
setType("year");
onClose();
}
const calculateMonthDifference = (date1, date2) => {
if (!(date1 instanceof Date) || !(date2 instanceof Date)) {
throw new Error("Both arguments must be Date objects");
}
setCurrent(date1)
let months = (date2.getFullYear() - date1.getFullYear()) * 12;
months -= date1.getMonth();
months += date2.getMonth();
setDelta(months);
};
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 = () => {
if (type === "time" || type === "month" || searchType === "range")
return "完成";
return "下一步";
};
const handleDateTimePickerChange = (value: (string | number)[]) => {
const year = value[0] as number;
const month = value[1] as number;
setSelected(new Date(year, month - 1, 1));
};
const onCancel = () => {
if (type === "month") {
setType("year");
} else if (type === "time") {
setType("year");
} else {
onClose();
}
};
useEffect(() => {
calendarRef.current?.gotoMonth(delta);
}, [delta])
useEffect(() => {
if (visible && value) {
setSelected(value || new Date());
setSelectedHour(value ? dayjs(value as Date).hour() : 8);
setSelectedMinute(value ? dayjs(value as Date).minute() : 0);
}
}, [value, visible]);
useEffect(() => {
if (type === "year" && pendingJump && calendarRef.current) {
calendarRef.current.jumpTo(pendingJump.year, pendingJump.month);
setPendingJump(null);
}
}, [type, pendingJump]);
console.log([selectedHour, selectedMinute], visible,type, "selectedHour, selectedMinute");
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}
>
{type === "year" && (
<View className={styles["calendar-container"]}>
<CalendarUI
ref={calendarRef}
type={searchType}
value={selected}
onChange={handleChange}
showQuickActions={false}
onHeaderClick={onHeaderClick}
/>
</View>
)}
{type === "month" && (
<PickerCommon
ref={pickerRef}
onChange={handleDateTimePickerChange}
type="month"
value={[
(selected as Date).getFullYear(),
(selected as Date).getMonth() + 1,
]}
/>
)}
{type === "time" && (
<PickerCommon
ref={hourMinutePickerRef}
type="hour"
value={[selectedHour, selectedMinute]}
/>
)}
</CommonPopup>
);
};
export default DialogCalendarCard;