Element-Plus日期选择组件封装农历日期

背景

在使用element-plus开发项目过程中,需要填入人员的生卒日期,经观察,对于大部分人来说,这类日期通常是农历日期,然而我们在系统建设过程中,对于日期字段,约定成俗的都会使用公历日期,这就存在一个问题,用户只记得自己的农历日期,那么在录入生卒日期的时候,往往就需要通过其他工具,查找到农历对应的公历日期,才能正确的录入系统中,并且,录入系统后,只能看到公历日期,不能直观的将农历日期反馈到用户,所以可能日期录入错误,也不能迅速的发现并修正,于是从实际需求出发,对element-plus组件库中的DatePicker组件进行自定义,在弹窗选择日期面板中,引入农历日期的显示,方便用户操作,减少错误发生。

组件设计

通过对element-plus组件库官方文档DatePicker 日期选择器 | Element Plus (element-plus.org)的查阅,DatePicker组件提供了一个默认的插槽,用于支持对弹出框内容的自定义,因此,我们需要借助此插槽来添加农历日期的显示。

根据日常使用惯例,大部分的日历工具,都是上面显示公历日期,下面显示对应的农历日期,如果日期是传统节日或者节气的,还会显示对应的节日或节气名称,因此,我们需要在自定义组件中,增加属性showFestival用于控制是否显示节日、showJieQi用于控制是否显示节气,如果都不显示,那么全都统一显示为农历日期天数。

我们知道,农历日期和公历日期是存在差异的,差异大的时候可能会相差一个月以上,然而日期选择组件的弹窗面板空间有限,因此我们需要将农历的月份融入日期中,也就是每个月的第一天显示当前农历月份,对于农历日期,用户往往还会注重当前年份的天干与地支,他们可以根据天干地支来进一步核实是否为当前年份,因此,我们还需要增加一个属性showLunarTip,用于控制显示当前日期的完整农历日期,如二〇二四年二月廿五 【甲辰(龙)年】,这样用户可以直观的看出当前日期正不正确,当然,出于对用户体验的改善,我们希望自定义组件更加人性化,比如,有时希望鼠标悬停到对应日期上,就马上弹出tip显示完整的农历日期信息,有时候,我希望鼠标悬停1秒以上才显示农历日期,减少对日期选择的干扰,因此我们再增加一个属性lunarTipShowAfter用于控制完整农历日期的弹出触发时常。

最终效果

效果图

工具选择

毋庸置疑,要显示公历对应的具体农历日期,肯定会存在日期间的换算,农历相对公历来说,规律性比较复杂,要完全自己实现公历转对应的农历,工作量较大,因此,我们优先选择三方工具,来完成两种历法的换算。

通过对几个工具库的对比,我最终选择了lunar (6tail.cn)工具库,它提供了丰富的接口,满足绝大部分场景下的使用需求,工具的强大性,请看官方文档介绍。

代码实现

因为项目使用vue3+typescript开发,因此自定义组件也是在此环境下完成。我们需要的是对原组件DatePicker的增强封装,因此我们的自定义组件需要保留绝大部分原组件的功能。

下面,直接贴出自定义组件的实现代码

<template><el-date-picker v-model="dateValue" v-bind="$props"><template #default="dateCell"><el-tooltip:disabled="!showLunarTip":show-after="lunarTipShowAfter":content="getLunarDateStr(dateCell.date)"placement="bottom"><div :class="getDateClass(dateCell)"><span class="solar-text">{{ dateCell.date.getDate() }}</span><span class="lunar-tex">{{ getLunarDay(dateCell.date) }}</span></div></el-tooltip></template></el-date-picker>
</template><script setup lang="ts">
import { JieQi, Solar } from 'lunar-typescript'
import { propTypes } from '@/utils/propTypes'
import { isEmpty } from '@/utils/is'
import { datePickerProps } from 'element-plus'
import type { DateCell } from 'element-plus/es/components/date-picker/src/date-picker.type'
// 带农历日期显示的选择组件
defineOptions({ name: 'LunarDatePicker' })const emit = defineEmits(['update:modelValue'])const props = defineProps({...datePickerProps,showFestival: propTypes.bool.def(true), // 是否显示节日showJieQi: propTypes.bool.def(true), // 是否显示节气showLunarTip: propTypes.bool.def(true), // 是否使用 tooltip 显示农历日期lunarTipShowAfter: propTypes.number.def(0) // 在触发后多久使用 tooltip 显示农历日期,单位毫秒
})const dateValue: Ref<typeof props.modelValue> = ref<typeof props.modelValue>('')watch(() => props.modelValue,(val: typeof props.modelValue) => {dateValue.value = val},{immediate: true}
)watch(() => dateValue.value,(val) => {emit('update:modelValue', val)}
)/*** 获取当前日期显示样式* @param dateCell 单元格日期信息*/
const getDateClass = (dateCell: DateCell) => {let cla = 'date-wrapper'if (dateCell.type === 'today') {cla += ' today'}if (dateCell.isCurrent || dateCell.isSelected || dateCell.start || dateCell.end) {cla += ' active'} else if (dateCell.inRange) {cla += ' in-range'}if (dateCell.disabled) {cla += ' disabled-date'}return cla
}/*** 获取农历 day 显示文字*/
const getLunarDay = (date) => {const solarDate = Solar.fromDate(date)const lunarDate = solarDate.getLunar()// 每月第一天显示月数if (lunarDate.getDay() == 1) {return lunarDate.getMonthInChinese() + '月'}// 显示节日if (props.showFestival) {const festivals = lunarDate.getFestivals()if (!isEmpty(festivals)) {return festivals[0]}}// 显示节气if (props.showJieQi) {const currJieQi: JieQi = lunarDate.getCurrentJieQi() as JieQiif (currJieQi && currJieQi?.getName()) {return currJieQi?.getName()}}return lunarDate.getDayInChinese()
}/*** 根据日历获取农历日期,包含年份干支和生肖*/
const getLunarDateStr = (date: Date): string => {const solarDate = Solar.fromDate(date)const lunarDate = solarDate.getLunar()return `${lunarDate.getYearInChinese()}${lunarDate.getMonthInChinese()}${lunarDate.getDayInChinese()}${lunarDate.getYearInGanZhi()}(${lunarDate.getYearShengXiao()})年】`
}
</script><style lang="scss" scoped>
.date-wrapper {position: relative;display: flex;align-items: center;flex-direction: column;padding: 4px 0;line-height: 18px;text-align: center;.solar-text {font-size: 14px;}.lunar-text {white-space: nowrap;}
}.today {font-weight: 700;color: var(--el-color-primary);
}.active {color: #fff;background-color: var(--el-datepicker-active-color);border-radius: 5px;
}.in-range {background-color: var(--el-datepicker-inrange-bg-color);
}.disabled-date {cursor: not-allowed;
}
</style>

相关代码

引入历法换算工具

npm i lunar-typescript

propTypes 工具代码

import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
import { CSSProperties } from 'vue'type PropTypes = VueTypesInterface & {readonly style: VueTypeValidableDef<CSSProperties>
}
const newPropTypes = createTypes({func: undefined,bool: undefined,string: undefined,number: undefined,object: undefined,integer: undefined
}) as PropTypesclass propTypes extends newPropTypes {static get style() {return toValidableType('style', {type: [String, Object]})}
}export { propTypes }

is 工具代码

// copy to vben-adminconst toString = Object.prototype.toStringexport const is = (val: unknown, type: string) => {return toString.call(val) === `[object ${type}]`
}export const isDef = <T = unknown>(val?: T): val is T => {return typeof val !== 'undefined'
}export const isUnDef = <T = unknown>(val?: T): val is T => {return !isDef(val)
}export const isObject = (val: any): val is Record<any, any> => {return val !== null && is(val, 'Object')
}export const isEmpty = <T = unknown>(val: T): val is T => {if (val === null) {return true}if (isArray(val) || isString(val)) {return val.length === 0}if (val instanceof Map || val instanceof Set) {return val.size === 0}if (isObject(val)) {return Object.keys(val).length === 0}return false
}export const isDate = (val: unknown): val is Date => {return is(val, 'Date')
}export const isNull = (val: unknown): val is null => {return val === null
}export const isNullAndUnDef = (val: unknown): val is null | undefined => {return isUnDef(val) && isNull(val)
}export const isNullOrUnDef = (val: unknown): val is null | undefined => {return isUnDef(val) || isNull(val)
}export const isNumber = (val: unknown): val is number => {return is(val, 'Number')
}export const isPromise = <T = any>(val: unknown): val is Promise<T> => {return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
}export const isString = (val: unknown): val is string => {return is(val, 'String')
}export const isFunction = (val: unknown): val is Function => {return typeof val === 'function'
}export const isBoolean = (val: unknown): val is boolean => {return is(val, 'Boolean')
}export const isRegExp = (val: unknown): val is RegExp => {return is(val, 'RegExp')
}export const isArray = (val: any): val is Array<any> => {return val && Array.isArray(val)
}export const isWindow = (val: any): val is Window => {return typeof window !== 'undefined' && is(val, 'Window')
}export const isElement = (val: unknown): val is Element => {return isObject(val) && !!val.tagName
}export const isMap = (val: unknown): val is Map<any, any> => {return is(val, 'Map')
}export const isServer = typeof window === 'undefined'export const isClient = !isServerexport const isUrl = (path: string): boolean => {const reg =/(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/return reg.test(path)
}export const isDark = (): boolean => {return window.matchMedia('(prefers-color-scheme: dark)').matches
}// 是否是图片链接
export const isImgPath = (path: string): boolean => {return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
}export const isEmptyVal = (val: any): boolean => {return val === '' || val === null || val === undefined
}

相关组件库版本

组件版本
vue^3.3.7
element-plus2.4.1
lunar-typescript^1.7.5
typescript5.2.2
vue-types^5.1.1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/789989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lecture 1 - Introduction

Lecture 1 - Introduction MIT 6.824 Distributed Systems 1、概念预览 分布式系统需要考虑的因素&#xff1a; Parallelism &#xff1a;并行性Fault tolerence &#xff1a;容错性Physicial &#xff1a;不同系统之间物理距离引起的通信问题Security &#xff1a;不同的计…

封装一个vue3的公共组件

在Vue 3中&#xff0c;封装公共组件的场景包括但不限于以下几种情况&#xff1a; 重复使用的组件&#xff1a;如果你发现某个组件在多个地方重复使用&#xff0c;那么将其封装成公共组件是很有意义的。比如&#xff0c;页面中的各种表单控件&#xff08;输入框、下拉框、日期选…

MySQL 数据学习笔记速查表(视图、存储过程、事务)

文章目录 十三、视图1、视图是什么&#xff1f;2、视图的特性&#xff1f;3、视图的作用&#xff1f;4、视图的用途&#xff1f;5、视图的使用&#xff1f;1、基本语法2、创建视图3、调用视图4、视图练习(1) 利用试图简化复杂的联结(2) 利用视图重新格式化检索出的数据(3) 利用…

Django详细教程(一) - 基本操作

文章目录 前言一、安装Django二、创建项目1.终端创建项目2.Pycharm创建项目&#xff08;专业版才可以&#xff09;3.默认文件介绍 三、创建app1.app介绍2.默认文件介绍 四、快速上手1.写一个网页步骤1&#xff1a;注册app 【settings.py】步骤2&#xff1a;编写URL和视图函数对…

Node爬虫:原理简介

在数字化时代&#xff0c;网络爬虫作为一种自动化收集和分析网络数据的技术&#xff0c;得到了广泛的应用。Node.js&#xff0c;以其异步I/O模型和事件驱动的特性&#xff0c;成为实现高效爬虫的理想选择。然而&#xff0c;爬虫在收集数据时&#xff0c;往往面临着诸如反爬虫机…

OSPF-基础、虚链路、overflow,缺省

OSPF 1、OSPF基础 2、区域内的路由计算 3、区域间的路由计算&#xff08;矢量&#xff09;&#xff08;区域间的防环原则&#xff09; 3.1、非骨干区域都与骨干区域相连。 3.2、骨干区域不会接收非骨干的3类LSA。 3.3、无论COST&#xff0c;1类LSA总是由于3类LSA。 ABR&…

快消企业数字化转型实战解析:探寻未来增长新动力

2024年&#xff0c;快消行业正站在数字化转型的风口浪尖。 “今年是过去十年最差的一年&#xff0c;但却可能是未来十年最好的一年。”这句话几乎成为了今年的流行语。 但是这句话是情绪&#xff0c;不是事实。未来十年&#xff0c;中国会成为全球最大的消费品市场&#xff0…

北方经贸经济类知网收录月刊投稿发表论文

《北方经贸》期刊是由国家新闻出版总署批准&#xff0c;黑龙江省教育厅主管&#xff0c;黑龙江省经济管理干部学院主办的经济类综合期刊。期刊融理论性、知识性、实践性于一体&#xff0c;立足龙江&#xff0c;辐射全国&#xff0c;面向世界&#xff0c;注重研究解决重大现实理…

vulnhub靶机: DC-9

dc-9靶机下载 将靶机设置为NAT模式&#xff0c;本次实验使用的内网网段为192.168.198.0/24&#xff0c;kali的ip为192.168.198.172 信息搜集 ip主机扫描&#xff1a; nmap -sP 192.168.198.0/24 确定靶机ip为192.168.198.171 主机端口扫描&#xff1a; nmap -T4 -A -v 192…

python基础——模块【模块的介绍,模块的导入,自定义模块,*和__all__,__name__和__main__】

&#x1f4dd;前言&#xff1a; 这篇文章主要讲解一下python基础中的关于模块的导入&#xff1a; 1&#xff0c;模块的介绍 2&#xff0c;模块的导入方式 3&#xff0c;自定义模块 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C语言入门基…

大数据面试专题 -- kafka

1、什么是消息队列&#xff1f; 是一个用于存放数据的组件&#xff0c;用于系统之间或者是模块之间的消息传递。 2、消息队列的应用场景&#xff1f; 主要是用于模块之间的解耦合、异步处理、日志处理、流量削峰 3、什么是kafka&#xff1f; kafka是一种基于订阅发布模式的…

通过SSH在苹果手机上查看系统文件:远程访问iOS文件系统的方法

​ 目录 引言 用户登录工具和连接设备 查看设备信息&#xff0c;电池信息 查看硬盘信息 硬件信息 查看 基带信息 销售信息 电脑可对手机应用程序批量操作 运行APP和查看APP日志 IPA包安装测试 注意事项 引言 苹果手机与安卓手机不同&#xff0c;无法直接访问系统文件…

python基于django协同算法的个性化音乐推荐系统的设计与实现

本个性化音乐推荐系统以Django作为框架&#xff0c;b/s模式以及MySql作为后台运行的数据库。本系统主要包括以下功能模块&#xff1a;首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;歌曲类型管理&#xff0c;明星歌手管理&#xff0c;歌曲音乐管理&#xff0c;歌曲…

权限提升技术:攻防实战与技巧

本次活动赠书1本&#xff0c;包邮到家。参与方式&#xff1a;点赞收藏文章即可。获奖者将以私信方式告知。 网络安全已经成为当今社会非常重要的话题&#xff0c;尤其是近几年来&#xff0c;我们目睹了越来越多的网络攻击事件&#xff0c;例如公民个人信息泄露&#xff0c;企业…

【Jenkins】关于账号,证书验证的设置问题

当你的电脑启动了Jenkins&#xff0c;这时候一定要小心更改管理员账号和密码~~~ 当你的电脑启动了Jenkins&#xff0c;这时候一定要小心更改管理员账号和密码~~~ 当你的电脑启动了Jenkins&#xff0c;这时候一定要小心更改管理员账号和密码~~~ 重要的事情说3遍&#xff0c;如…

阿里云数据库服务器价格表查询_一张表精准报价

阿里云数据库服务器价格表&#xff0c;优惠99元一年起&#xff0c;ECS云服务器2核2G、3M固定带宽、40G ESSD Entry云盘&#xff0c;优惠价格99元一年&#xff1b;阿里云数据库MySQL版2核2G基础系列经济版99元1年、2核4GB 227.99元1年&#xff0c;云数据库PostgreSQL、SQL Serve…

财经界投稿发表论文知网收录

《财经界》是由国家新闻出版总署批准、国家发展计划委员会主管、国家信息中心主办的正规国家级经济类期刊。本刊为大型财经刊物&#xff0c;旨在介绍国家宏观经济政策、经济环境、经济信息和分析经济热点问题。杂志面向各级政府决策层、财经高管人员、研究机构的专家学者、资本…

ES学习日记(九)-------logstash导入数据

一、安装和下载 es官网下载地址 官方介绍:Logstash是开源的服务器端数据处理管道&#xff0c;能够同时从多个来源采集数据&#xff0c;转换数据&#xff0c;然后将数据发送到您最喜欢的“存储库”中。(我们的存储库当然是 Elasticsearch。) 下载和ES一样的版本(很重要,必须这…

轻量应用服务器16核32G28M腾讯云租用优惠价格4224元15个月

腾讯云16核32G服务器租用价格4224元15个月&#xff0c;买一年送3个月&#xff0c;配置为&#xff1a;轻量16核32G28M、380GB SSD盘、6000GB月流量、28M带宽&#xff0c;腾讯云优惠活动 yunfuwuqiba.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云16核32G服务器租用价格 腾讯…

Acwing.1388 游戏(区间DP对抗思想)

题目 玩家一和玩家二共同玩一个小游戏。 给定一个包含 N个正整数的序列。 由玩家一开始&#xff0c;双方交替行动。 每次行动可以在数列的两端之中任选一个数字将其取走&#xff0c;并给自己增加相应数字的分数。&#xff08;双初始分都是 0分&#xff09; 当所有数字都被…