前言
终于开启第二篇文章了,上一篇还是三年前,本人技术栈一直以vue为主,最近开始接触一些react的项目,简单写了一些组件,感觉和vue核心思想差不多,不过差别也是有的。好了,框架不重要,接下来进入正题。
效果图
话不多说,先上效果图,该组件使用react开发框架,基于ant-design 里的Calendar 组件,使用了lunar-javascript日历工具库对日历单元格进行农历日期的渲染。过程很简单,但在没找到门路之前也走了很多弯路,所以在此记录一下。
思路
先把我们要用到的插件引进来,这里用到了ant-design的模态框,日历,tag标签,以及lunar日历库。
import { Modal, Calendar, Tag} from 'antd';
import { CloseCircleOutlined } from '@ant-design/icons';
import { Lunar,Solar } from 'lunar-javascript';
接下来放我们的日历组件
const [isModalOpen, setIsModalOpen] = useState(false)
const [onSelectDate, setOnSelectDate] = useState([])
const [calendarDate, setCalendarDate] = useState(moment(new Date()));
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false)
};
return (
<>
<button onClick={showModal} >农历日期选择</button>
<Modal
title="农历日期选择"
getContainer={false}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
footer={[<Button className="clo-btn" key="back" onClick={handleOk}>确认</Button>]}
>
<Calendar dateCellRender={dateCellRender} className="site-calendar" fullscreen={false} onSelect={onSelect}/>
<div className='selectedDate'>已选:{renderSelectedDate()}</div>
</Modal>
</>
)
利用dateCellRender对Calendar日历单元格的重写,加上农历日期:
const dateCellRender = (date) => {
const d = date._d
const solar = Solar.fromDate(d);
const lunar = solar.getLunar();
// 将数字形式的农历年月日放到数组里
const lunarDateArab = [lunar.getYear(),lunar.getMonth(),lunar.getDay()]
// 将中文形式的农历年月日放到数组里
const lunarDateChin = [Lunar.fromDate(d).toString().substr(0,4),Lunar.fromDate(d).toString().substr(5,2),Lunar.fromDate(d).toString().substr(7,2)]
const lunarSL = Lunar.fromYmd(lunarDateArab[0], lunarDateArab[1], lunarDateArab[2]);
const festival = lunarSL.getFestivals(); // 获取农历节日
const jieQi = lunarSL.getJieQi() // 获取节气
let showDate = lunarDateChin[2]
if(lunarDateChin[2] == '初一') {
showDate = lunarDateChin[1]
}
if (jieQi) {
showDate = jieQi
}
if (festival.length > 0) {
showDate = festival[0]
}
return (
<div>
<div className='lunarStyle'>{showDate}</div>
</div>
);
};
到这里,农历日期展示就可以了。接下来是对农历的日期进行选择、展示、删除的功能。
// 点击日历选择某个日期
const onSelect = (value, mode) => {
let flag = true
// 这里防止用户选择年和月的时候选中日期
if (!calendarDate.isSame(value, 'month') || !calendarDate.isSame(value, 'year')) {
setCalendarDate(value);
return;
}
const d = value._d
const solar = Solar.fromDate(d);
const lunar = solar.getLunar();
const lunarDateArab = [lunar.getYear(),lunar.getMonth(),lunar.getDay()]
const lunarDateChin = [Lunar.fromDate(d).toString().substr(0,4),Lunar.fromDate(d).toString().substr(5,2),Lunar.fromDate(d).toString().substr(7,2)]
const lunarSL = Lunar.fromYmd(lunarDateArab[0], lunarDateArab[1], lunarDateArab[2]);
const festival = lunarSL.getFestivals();
const jieQi = lunarSL.getJieQi()
let arr = JSON.parse(JSON.stringify(onSelectDate))
let showDate = lunarDateChin[1] + lunarDateChin[2]
if (festival.length > 0) {
showDate = festival[0]
}
arr.forEach((e) => {
if(e.chin == showDate) {
flag = false
}
})
// 加flag防止选择到重复的日期
if(flag) {
// 由于后端需要农历的数字形式和中文形式,所以这里设置了chin 放农历中文,arab放数字
arr.push({'chin': showDate, 'arab': lunar.getMonth() + '.' + lunar.getDay()})
setOnSelectDate(arr)
}
};
// 已选tag签的渲染
const renderSelectedDate = () => {
return onSelectDate.map((item, index) => {
return (
<span className='selectedDate-item'>
<Tag key={item.chin} closable closeIcon={<CloseCircleOutlined />} onClose={() => handleClose(item)}>
{item.chin}
</Tag>
</span>
)
})
}
// 删除已选日期
const handleClose = (value) => {
let arr = JSON.parse(JSON.stringify(onSelectDate))
arr = arr.filter(item => item.chin !== value.chin)
setOnSelectDate(arr)
};
问题
在开发过程中遇到了两个问题,一个是Calendar组件的onSelect,用户无论点击日期还是月份年份都会触发onSelect,所以加了下面这个判断,calendarDate里存放日历里上一个选中的日期,当触发了onSelect时对选中日期和上一个日期年份和月份进行对比,如果年份月份有一个变了,说明用户可能对年份和月份进行了选择,如果年份和月份都没变,则说明用户选择了某个日期。
if (!calendarDate.isSame(value, 'month') || !calendarDate.isSame(value, 'year')) {
setCalendarDate(value);
return;
}
第二个问题是tag标签的,除了最后一个标签点击删除没问题外,点击其他标签的删除会直接删掉两个tag,这是由于之前为了图省事,tag标签里的key的值简单粗暴的用了index,而tag标签在删除的时候会通过这个关键 key 来删除数组中的对应元素,而index在标签删除后会发生改变,所以这里的key一定要用一个唯一的id。
写在最后
以上就是关于农历日历的分享,简单但细节满满,上面代码其实可以更加精简一些,但我马不停蹄的奔向下一需求了,就这么着了。欢迎大神们批评指正,也希望能给困惑中的你一点点解题思路~