日期范围选择组件

This commit is contained in:
2025-09-25 00:10:24 +08:00
parent dd1136d1e6
commit d99d3d87a9
4 changed files with 374 additions and 258 deletions

View File

@@ -6,7 +6,7 @@ import { PickerCommon, PickerCommonRef } from '@/components/Picker'
import dayjs from 'dayjs'
import styles from './index.module.scss'
export interface DialogCalendarCardProps {
value?: Date
value?: Date | Date[]
searchType?: 'single' | 'range' | 'multiple'
onChange?: (date: Date | Date[]) => void
visible: boolean
@@ -22,7 +22,7 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
value,
onChange,
}) => {
const [selected, setSelected] = useState<Date>(value || new Date())
const [selected, setSelected] = useState<Date | Date[]>(value || new Date())
const calendarRef = useRef<CalendarUIRef>(null);
const [type, setType] = useState<'year' | 'month' | 'time'>('year');
const [selectedHour, setSelectedHour] = useState(8)
@@ -32,6 +32,11 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
const [pendingJump, setPendingJump] = useState<{ year: number; month: number } | null>(null)
const handleConfirm = () => {
if (type === 'year') {
if (searchType === 'range') {
if (onChange) onChange(selected);
onClose();
return;
}
// 年份选择完成后,进入月份选择
setType('time')
} else if (type === 'month') {
@@ -40,7 +45,13 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
if (value) {
const year = value[0] as number
const month = value[1] as number
if (searchType === 'range') {
const delta = calculateMonthDifference(selected as Date, new Date(year, month - 1, 1))
console.log('xxxxx', calendarRef.current)
calendarRef.current?.gotoMonth(delta)
} else {
setSelected(new Date(year, month - 1, 1))
}
setPendingJump({ year, month })
}
setType('year')
@@ -54,18 +65,31 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
setSelectedMinute(minute)
const hours = hour.toString().padStart(2, '0')
const minutes = minute.toString().padStart(2, '0')
const finalDate = new Date(dayjs(selected).format('YYYY-MM-DD') + ' ' + hours + ':' + minutes)
const finalDate = new Date(dayjs(selected as Date).format('YYYY-MM-DD') + ' ' + hours + ':' + minutes)
if (onChange) onChange(finalDate)
}
onClose()
}
}
const calculateMonthDifference = (date1, date2) => {
if (!(date1 instanceof Date) || !(date2 instanceof Date)) {
throw new Error('Both arguments must be Date objects');
}
let months = (date2.getFullYear() - date1.getFullYear()) * 12;
months -= date1.getMonth();
months += date2.getMonth();
return months;
}
const handleChange = (d: Date | Date[]) => {
console.log('handleChange', d)
if (searchType === 'range') {
if (Array.isArray(d)) {
if (d.length === 2) {
onChange?.(d as Date[])
setSelected(d as Date[])
return
}
}
@@ -77,6 +101,7 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
}
}
const onHeaderClick = (date: Date) => {
console.log('onHeaderClick', date)
setSelected(date)
setType('month')
}
@@ -101,8 +126,8 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
useEffect(() => {
if (visible && value) {
setSelected(value || new Date())
setSelectedHour(value ? dayjs(value).hour() : 8)
setSelectedMinute(value ? dayjs(value).minute() : 0)
setSelectedHour(value ? dayjs(value as Date).hour() : 8)
setSelectedMinute(value ? dayjs(value as Date).minute() : 0)
}
}, [value, visible])
@@ -146,7 +171,7 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
ref={pickerRef}
onChange={handleDateTimePickerChange}
type="month"
value={[selected.getFullYear(), selected.getMonth() + 1]}
value={[(selected as Date).getFullYear(), (selected as Date).getMonth() + 1]}
/>
}

View File

@@ -5,6 +5,7 @@ import images from '@/config/images'
import styles from './index.module.scss'
import { getMonth, getWeekend, getWeekendOfCurrentWeek } from '@/utils/timeUtils'
import { PopupPicker } from '@/components/Picker/index'
import dayjs from 'dayjs'
interface NutUICalendarProps {
type?: 'single' | 'range' | 'multiple'
value?: string | Date | String[] | Date[]
@@ -16,7 +17,8 @@ interface NutUICalendarProps {
}
export interface CalendarUIRef {
jumpTo: (year: number, month: number) => void
jumpTo: (year: number, month: number) => void,
gotoMonth: (delta: number) => void,
}
const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
@@ -60,7 +62,7 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
useEffect(() => {
if (Array.isArray(value) && value.length > 0) {
setSelectedValue(value.map(item => new Date(item)))
setCurrent(new Date(value[0]))
setCurrent(new Date(value[0] as Date))
}
if ((typeof value === 'string' || value instanceof Date) && value) {
setSelectedValue(new Date(value))
@@ -71,11 +73,13 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
useImperativeHandle(ref, () => ({
jumpTo: (year: number, month: number) => {
calendarRef.current?.jumpTo(year, month)
},
gotoMonth: (delta: number) => {
gotoMonth(delta)
}
}))
const handleDateChange = (newValue: any) => {
console.log('xxxxxxxxxxxxxxxxxxxxxx', newValue)
setSelectedValue(newValue)
onChange?.(newValue as any)
}
@@ -87,6 +91,7 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
}
const gotoMonth = (delta: number) => {
console.log('aaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbb', delta)
const base = current instanceof Date ? new Date(current) : new Date()
base.setMonth(base.getMonth() + delta)
const next = startOfMonth(base)
@@ -101,6 +106,7 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
}
const handleHeaderClick = () => {
console.log('handleHeaderClick', current)
onHeaderClick && onHeaderClick(current)
setvisible(true)
}
@@ -119,7 +125,7 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
const today = new Date()
if (date === today.getDate() && month === today.getMonth() + 1 && year === today.getFullYear()) {
return (
<View class="day-container">
<View className="day-container">
{date}
</View>
)
@@ -166,6 +172,15 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(({
</View>
}
<View className={`${styles['calendar-card']} ${isBorder ? styles['border'] : ''}`}>
{
type === 'range' && (
<View className={styles['date-range-container']}>
<Text className={`${styles['date-text-placeholder']} ${value?.[0] ? styles['date-text'] : ''}`}>{value?.[0] ? dayjs(value?.[0] as Date).format('YYYY-MM-DD') : '起始时间'}</Text>
<Text></Text>
<Text className={`${styles['date-text-placeholder']} ${value?.[1] ? styles['date-text'] : ''}`}>{value?.[1] ? dayjs(value?.[1] as Date).format('YYYY-MM-DD') : '结束时间'}</Text>
</View>
)
}
{/* 自定义头部显示周一到周日 */}
<View className={styles['header']}>
<View className={styles['header-left']} onClick={handleHeaderClick}>

View File

@@ -1,6 +1,7 @@
.calendar-card {
background: #fff;
border-radius: 16px;
&.border {
border-radius: 12px;
border: 0.5px solid rgba(0, 0, 0, 0.12);
@@ -17,19 +18,46 @@
padding: 9px 4px 11px 4px;
height: 24px;
}
.date-range-container {
height: 55px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
color: #000;
padding: 0 4px;
font-size: 17.68px;
}
.date-text-placeholder {
font-family: PingFang SC;
font-weight: 600;
font-style: Semibold;
font-size: 17.68px;
color: #3C3C4399;
}
.date-text {
color: #000;
}
.header-left {
display: flex;
align-items: center;
gap: 6px;
}
.header-text {
font-size: 17px;
font-weight: 600;
color: #000;
}
.header-actions {
display: flex;
width: 60px;
.arrow-left-container {
display: flex;
align-items: center;
@@ -37,6 +65,7 @@
width: 50%;
flex: 1;
}
.arrow-right-container {
display: flex;
align-items: center;
@@ -45,15 +74,18 @@
flex: 1;
}
}
.month-arrow {
width: 8px;
height: 24px;
}
.arrow {
width: 10px;
height: 24px;
position: relative;
}
.arrow.left {
left: 9px;
transform: rotate(-180deg);
@@ -64,6 +96,7 @@
grid-template-columns: repeat(7, 1fr);
padding: 0 0 4px 0;
}
.week-item {
text-align: center;
color: rgba(60, 60, 67, 0.30);
@@ -76,6 +109,7 @@
grid-template-columns: repeat(7, 1fr);
padding: 8px 0;
}
.week-day {
text-align: center;
color: rgba(60, 60, 67, 0.30);
@@ -89,6 +123,7 @@
gap: 8px 0;
padding: 4px 0 16px;
}
.cell {
height: 44px;
display: flex;
@@ -97,12 +132,15 @@
font-size: 20px;
position: relative;
}
.cell.empty {
opacity: 0;
}
.cell.disabled {
color: rgba(0, 0, 0, 0.2);
}
.cell-text.selected {
width: 44px;
height: 44px;
@@ -152,6 +190,7 @@
display: flex;
gap: 12px;
}
.btn {
flex: 1;
height: 44px;
@@ -161,6 +200,7 @@
align-items: center;
justify-content: center;
}
.btn.primary {
background: #000;
color: #fff;
@@ -241,8 +281,14 @@
.nut-calendarcard-header {
display: none !important;
}
.nut-calendarcard-content {
.nut-calendarcard-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
justify-items: center;
&:first-child {
display: none !important;
}
@@ -250,10 +296,12 @@
}
}
.nut-calendarcard-day {
margin-bottom: 0px !important;
height: 44px;
width: 44px !important;
&.active {
background-color: #000 !important;
color: #fff !important;
@@ -264,19 +312,24 @@
justify-content: center;
width: 44px !important;
font-size: 24px !important;
.day-container {
background-color: transparent !important;
}
}
&.weekend {
color: rgb(0, 0, 0) !important;
&.active {
color: #fff !important;
}
}
}
.nut-calendarcard-day-inner {
font-size: 20px;
.day-container {
background-color: #f5f5f5;
border-radius: 22px;
@@ -288,5 +341,22 @@
font-size: 24px;
}
}
.nut-calendarcard-day.start,
.nut-calendarcard-day.end {
background-color: #000;
border-radius: 50%;
color: #fff !important;
}
.nut-calendarcard-day-inner .day-container {
background-color: unset;
color: unset;
}
.nut-calendarcard-day.mid {
background-color: rgba(0, 0, 0, 0.12);
color: #000;
border-radius: 50%;
}
}

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { View, Text, Button } from "@tarojs/components";
import Taro, { useDidShow } from "@tarojs/taro";
import dayjs from 'dayjs'
import "./index.scss";
import { DialogCalendarCard } from "@/components/index";
@@ -56,8 +57,13 @@ const DownloadBill: React.FC = () => {
}
};
const [currentTimeValue, setCurrentTimeValue] = useState<Date | Date[]>(new Date());
const handleConfirm = (val) => {
const handleConfirm = (val: Date[]) => {
setCurrentTimeValue(val);
const [start, end] = val;
setDateRange({
start: dayjs(start).format("YYYY-MM-DD"),
end: dayjs(end).format("YYYY-MM-DD"),
});
};
return (
<View className="download_bill_page">