前言:依赖DatePicker二开的一个组件,主要是对Datepicker默认的展示形态进行一个处理(强烈建议Antd后续出自定义),如果React有现成能达到这种要求的包,求大佬告知(感觉肯定是有的,就是我不知道,焯)。
设计需求
需求展示形态单个模块,这里实现的时候用了Radio.Group作形态主体,antd的DatePicker组件里其实有一个api为variant(形态变体),只不过只能调整边框和背景,类似ghost效果,但感觉后面版本会支持自定义。
代码实现(ts)
这两个代码是放在一个文件里的,这里拆开是方便阅读。
使用
<RadioTimePciker
onFinished={(date, dateString, type) => {
console.log(date, dateString, type);
}}
/>
RadioGroup形态代码
const raditoStyle: React.CSSProperties = {
width: "60px",
height: "32px",
textAlign: "center",
paddingInline: "2px",
}
interface timeProps {
onFinished: (date: dayjs.Dayjs | dayjs.Dayjs[] | undefined, dateString: string | string[], dateType: string) => void | undefined
}
// 日历形态
const RadioTimePciker = ({ onFinished }: timeProps) => {
const SubtitleRef = useRef(null);
const [dateValue, setDateValue] = useState<dayjs.Dayjs | dayjs.Dayjs[] | undefined>(undefined)
const [left, SetLeft] = useState(0) //pup偏移量
// 存放各个时间值
const [state, setState] = useSet({
picker_show: false,
picker_type: "30day",
timeString: undefined
})
const { picker_type, picker_show, } = state
useEffect(() => {
switch (picker_type) {
case "7day":
onFinished(dateValue, "7day", picker_type)
break;
case "30day":
onFinished(dateValue, "30day", picker_type)
break;
case "week":
const week = dateValue as dayjs.Dayjs
onFinished(dateValue, getWeekRange(week), picker_type)
break;
case "month":
const month = dateValue as dayjs.Dayjs
onFinished(dateValue, getMonthRange(month), picker_type)
break;
case "custom":
const custom = dateValue as dayjs.Dayjs[]
const dateString = [custom[0].format("YYYY-MM-DD"), custom[1].format("YYYY-MM-DD")]
onFinished(dateValue, dateString, picker_type)
break;
case "date":
const date = dateValue as dayjs.Dayjs
onFinished(dateValue, date.format("YYYY-MM-DD"), picker_type)
break;
}
// console.log(dateValue.subtract(7, "day").format('YYYY-MM-DD'));
}, [dateValue])
// 日期选择完成
const onDateChange = (date: any) => {
setDateValue(date)
setState({
picker_show: false
})
};
// tab点击
const radioClick = (e: any) => {
const type = e.target.value
setState({ picker_type: type })
if (type !== "7day" && type !== "30day") {
setState({ picker_show: true, })
const left_list: { [key: string]: number } = {
"date": -108,
"week": -48,
"month": 12,
"custom": -216,
}
SetLeft(left_list[type])
} else {
setState({ picker_show: false, })
}
}
const cancel = () => {
setState({ picker_show: false, })
}
// tab切换
const onRadioChange = (e: any) => {
const type = e.target.value
if (type === "month") {
setDateValue(dayjs().subtract(1, "month"))
} else if (type === "week") {
setDateValue(dayjs().subtract(1, "week"))
} else if (type === "date") {
setDateValue(dayjs().subtract(1, "day"))
}
}
return <Radio.Group
ref={SubtitleRef}
defaultValue={"30day"}
style={{ position: "relative" }}
className={styles.radioGroup}
onChange={onRadioChange}
>
<Radio.Button onClick={radioClick} style={raditoStyle} value="7day">7天</Radio.Button>
<Radio.Button onClick={radioClick} style={raditoStyle} value="30day">30天</Radio.Button>
<Radio.Button onClick={radioClick} style={raditoStyle} value="date">日</Radio.Button>
<Radio.Button onClick={radioClick} style={raditoStyle} value="week">周</Radio.Button>
<Radio.Button onClick={radioClick} style={raditoStyle} value="month">月</Radio.Button>
<Radio.Button onClick={radioClick} style={raditoStyle} value="custom">自定义</Radio.Button>
<PopupPicker
left={left}
top={picker_type === "custom" ? 25 : 35}
type={picker_type as any}
dateValue={dateValue}
onDateChange={onDateChange}
show={picker_show} // 控制日历是显示隐藏
onCancel={cancel}
Node={SubtitleRef.current}
/>
</Radio.Group>
}
export default RadioTimePciker;
两个format代码(上次忘记补充了,dayjs天下无敌哈哈哈)
// 根据日期 获取当前日所在周的开始和结束
export const getWeekRange = (date: Date | dayjs.Dayjs | undefined) => {
const startOfWeek = dayjs(date).startOf('week').format('YYYY-MM-DD');
const endOfWeek = dayjs(date).endOf('week').format('YYYY-MM-DD');
return [startOfWeek, endOfWeek];
}
// 获取上一个月的开始和结束
export const getMonthRange = (date: Date | dayjs.Dayjs | undefined) => {
const startOfWeek = dayjs(date).startOf('month').format('YYYY-MM-DD');
const endOfWeek = dayjs(date).endOf('month').format('YYYY-MM-DD');
return [startOfWeek, endOfWeek];
}
Popup面板代码
import React, { useEffect, useState, useRef } from 'react';
import { DatePicker, Button, DatePickerProps, Radio, message } from 'antd';
import type { RangePickerProps } from 'antd/es/date-picker';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import styles from "./index.less"
import { useSet } from '@/utils/hooks';
import { getMonthRange, getWeekRange } from '@/utils/format';
dayjs.locale('zh-cn');
const { RangePicker } = DatePicker
interface Date {
style?: any;
className?: any;
dateValue: any
top: number; // 绝对定位距离面板的top
left: number; // 绝对定位距离面板的left
onDateChange: any; // 确定时间回调事件
show: boolean; // 显示弹窗
Node?: any; // 定义浮层的容器
onCancel: () => void
type: "date" | "week" | "month" | "year" | "7day" | "30day" | "custom"
}
// 日历面板
export const PopupPicker = ({ top, left, onDateChange, show, onCancel, Node, dateValue, type = "date" }: Date) => {
const [dateState, setdateState] = useState(false);
const [keyValue, setKeyValue] = useState('');
const [isRange, setIsRange] = useState(false)
const [dateType, setType] = useState<"date" | "week" | "month" | "year">("date")
const [rangeValue, setRangeValue] = useState<any>([])
const picker: React.MutableRefObject<any> = useRef();
useEffect(() => {
if (type !== "7day" && type !== "30day") {
switch (type) {
case "custom":
setType("date")
setIsRange(true);
break;
default:
setType(type)
setIsRange(false)
break;
}
}
}, [type])
useEffect(() => {
setdateState(show);
// console.log(show);
}, [show]);
const reDate = () => {
setKeyValue(dayjs().format());
};
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
if (type === "month") {
return current && current >= dayjs().startOf('month')
} else if (type === "week") {
return current && current >= dayjs().startOf('week')
}
return current && current >= dayjs().startOf('day');
};
// datepickerChange
const handleCalendarChange: DatePickerProps['onChange'] = (
date,
dateString,
) => {
onDateChange(date);
setdateState(false);
picker.current.blur();
reDate();
};
// rangePickerChange
const rangePickerChange = (dates: any, dateString: any) => {
setRangeValue(dates)
}
const confirm = () => {
if (rangeValue.length !== 0) {
onDateChange(rangeValue);
setdateState(false);
picker.current.blur();
reDate();
} else {
message.destroy()
message.info("请先选择日期范围")
}
}
return (
<>
{isRange ?
<RangePicker
key={keyValue}
value={rangeValue}
style={{ transform: "scale(0)", width: 0, height: 0, padding: 0, margin: 0, border: "none" }}
renderExtraFooter={() => {
return <>
<Button onClick={confirm} type='link' style={{ width: "50px" }}>确认</Button>
<Button onClick={() => setRangeValue([])} type='link' style={{ width: "50px", color: "red" }}>重选</Button>
<Button onClick={() => { onCancel() }} type='link' style={{}}>取消</Button>
</>
}}
open={dateState}
ref={picker}
disabledDate={disabledDate}
onChange={rangePickerChange}
popupClassName={styles.rangedropdownClassName}
popupStyle={{
left: `${left}px`,
top: `${top}px`,
position: "absolute",
}}
getPopupContainer={() => Node}
/>
: <DatePicker
key={keyValue}
value={dateValue}
picker={dateType}
style={{ display: 'none' }}
open={dateState}
ref={picker}
showNow={false}
// showWeek={true}
disabledDate={disabledDate}
onChange={handleCalendarChange}
popupClassName={styles.datedropdownClassName}
renderExtraFooter={() => {
return <>
<Button onClick={() => { onCancel() }} type='link' style={{}}>取消</Button>
</>
}}
popupStyle={{
left: `${left}px`,
top: `${top}px`,
position: "absolute",
}}
getPopupContainer={() => Node}
/>}
</>
);
};
less样式代码
.rangedropdownClassName {
:global {
.ant-picker-date-range-wrapper {
.ant-picker-range-arrow {
display: none;
}
}
.ant-picker-footer-extra {
direction: rtl;
}
}
}
.datedropdownClassName {
// transform: scale(1);
// animation-name: antSlideUpIn;
// position: absolute;
:global {
.ant-picker-footer-extra {
text-align: center;
}
}
}
.radioGroup {
:global {
.ant-radio-button-wrapper {
border-color: #e5e5e5;
color: #333333;
background-color: #f8f9fa;
}
.ant-radio-button-wrapper-checked {
color: #5050e6;
border-color: #5050e6;
}
}
}
实现效果
备注:7天和30天的value暂时没确定,根据服务端入参自己调整是否需要处理dateValue数值,我觉得应该是不需要,和服务端约定好就行。
组件目前我就写了一个回调(返回date,dateString,type),用来处理获取到的时间类型和时间值string,进行数据处理。
日
周
月
自定义
注意点
实现的思路就是把原来的DatePicker这个组件去掉,只保留日期popup面板来进行依附(还好antd能单独对日历面板自定义),但是有个注意点是在使用RangePicker的时候就不能用同样的方法去实现了,不能再将display设置为none,rangePicker在使用的时候面板会对形态获取焦点,获取不到无法填入值是不能进行范围选择的,我这里是用了取巧的方法,样式代码放在下面了。隐藏的那三个方式都不大行,反正只要看不见不影响操作就行,还有一点就是如果还有别的rangePicker样式可能会覆盖,建议采用我这种样式引入方式,global可以控制为局部样式
style={{ transform: "scale(0)", width: 0, height: 0, padding: 0, margin: 0, border: "none" }}