<template> <div class="date-picker relative" ref="date_picker"> <div class="input-date w-36 h-8 rounded overflow-hidden relative" :style="{ backgroundColor: '#2B2D3A' }" > <input type="text" class="border-none outline-none w-full h-full px-3" :style="{ backgroundColor: '#2B2D3A' }" placeholder="请选择日期" @blur="BlurHandel" @focus="FocusHandel" v-model="value" /> <i class="iconfont cursor-pointer text-white absolute right-2 top-1/2 -translate-y-1/2" :class="icon" @mouseenter="iconMouenterHandel" @mouseleave="icon = 'icon-rili'" @click="iconClickHandel" ></i> </div> <div ref="calendar" class="calendar-card" v-show="isFoucus"> <div class="flex w-full h-9 justify-between items-center" :style="{ color: '#000' }"> <!-- 上一年 --> <button v-if="isPreYear" @click="minusYear(true)"> <i class="iconfont icon-zuojiantou"></i> </button> <!-- 上一月 --> <button @click="minusMonth" v-if="isPreMonth"> < </button> <div class="_button" @mouseover="isYearShow = true" @mouseout="isYearShow = false"> <span> {{ date.year }}年</span> <div class="box_year" v-show="isYearShow"> <ul> <li :class="{ active: date.year === year }" v-for="(year, index) in yearList" :key="year" @click="clickCurrentYearHandel(year, index)" >{{ year }}</li > </ul> </div> <!-- {{ date.date }} --> </div> <div class="_button" @mouseover="isMouthShow = true" @mouseout="isMouthShow = false"> <span> {{ (date.month as number) + 1 }}月</span> <div class="box_month" v-show="isMouthShow"> <ul> <li :class="{ active: (date.month as number) + 1 === m }" v-for="(m, index) in monthList" :key="m" @click="clickCurrentMonthHandel(m, index)" >{{ m }}月</li > </ul> </div> </div> <!-- 下一月 --> <button @click="plusMonth" v-if="isNextMonth">></button> <!-- 下一年 --> <button @click="plusYear(true)" v-if="isNextYear"> <i class="iconfont icon-youjiantou"></i> </button> </div> <div class="calendar-content"> <ul class="ul-week"> <li class="li-week" v-for="item in week" :key="item">{{ item }}</li> </ul> <ul class="ul-day"> <li class="li-day" v-for="(item, index) in days" :key="index" :isThisMonth="item?.isThisMonth" @click="handleClick(item)" > {{ item?.date }} </li> </ul> </div> <div class="footer"> <ul> <li @click="clearAllHandel">清空</li> <li @click="currentTodayHandel">今天</li> <li @click="okHandel">确定</li> </ul> </div> </div> </div> </template>
<script lang="ts" setup> import { unref, watch, computed, ref, reactive, onMounted, onUnmounted } from 'vue'; import { Idate, IDateDate } from '../props';
defineOptions({ name: 'DatePicker', });
defineProps({ /*是否显示上一月箭头 */ isPreMonth: { type: Boolean as PropType<boolean>, default: false, }, /*是否显示下一月箭头 */ isNextMonth: { type: Boolean as PropType<boolean>, default: false, }, /*是否显示上一年箭头 */ isPreYear: { type: Boolean as PropType<boolean>, default: true, }, /*是否显示下一年箭头 */ isNextYear: { type: Boolean as PropType<boolean>, default: true, }, });
const emit = defineEmits(['change', 'ok']);
const isYearShow = ref(false); const isMouthShow = ref(false); const isActiveLi = ref<any>(null); const isActiveLiM = ref<any>(null); const isFoucus = ref(false); const value = ref<string | undefined>(''); const calendar = ref(null); const date_picker = ref(null); const icon = ref('icon-rili');
const monthList = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
const date: Idate = reactive({ year: '', month: '', date: '', h: 0, m: 0, s: 0, });
const current: Idate = reactive({ year: '', month: '', date: '', h: 0, m: 0, s: 0, });
const yearList = computed(() => { let d = new Date(); let list: Array<number> = [d.getFullYear() as number]; for (let i = 1; i < 10; i++) { list.push((d.getFullYear() as number) - i); } return list; });
const week = ['日', '一', '二', '三', '四', '五', '六'];
const days = ref<Array<Idate>>([]);
const cache = ref('');
const bodyClickHandel = (e: MouseEvent) => { if ((date_picker.value as unknown as HTMLElement).contains(e.target as HTMLElement)) { return; } if (isFoucus.value) { console.log('关闭'); close(); } };
onMounted(() => { initDate(); document.body.addEventListener('click', bodyClickHandel); });
onUnmounted(() => { document.body.removeEventListener('click', bodyClickHandel); });
/** * 初始化日期(年月日) */ const initDate = () => { let d = new Date(); date.year = d.getFullYear(); date.month = d.getMonth(); date.date = d.getDate();
current.year = d.getFullYear(); current.month = d.getMonth(); current.date = d.getDate();
createCalendar(current.year, current.month); };
/** * 初始化时间(时分秒) */ const initTime = () => { let d = new Date(); date.h = d.getHours(); date.m = d.getMinutes(); date.s = d.getMilliseconds();
current.h = d.getHours(); current.m = d.getMinutes(); current.s = d.getMilliseconds().toString().padStart(3, '0').slice(0, 2); };
/** * 格式话时间 */ const getCurrentTime = (hours, minutes, seconds) => { let strDate = ''; if (!hours && !minutes && !seconds) { const now = new Date(); let hours: number | string = now.getHours(); let minutes: number | string = now.getMinutes(); let seconds: number | string = now.getSeconds(); hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; seconds = seconds < 10 ? '0' + seconds : seconds; strDate = `${hours}:${minutes}:${seconds}`; } else if (hours && minutes && seconds) { hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; seconds = seconds < 10 ? '0' + seconds : seconds; strDate = `${hours}:${minutes}:${seconds}`; }
return strDate; };
/** * 校验日期格式 */ const checkDateFormat = (dateString: string) => { const pattern = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; return pattern.test(dateString); };
/** * 需要返回的日期数据 */ const selectDate = computed(() => { if (!current.date) return null; return { $d: new Date( `${current.year}-${ current.month.toString().length <= 1 ? '0' + ((current.month as number) + 1).toString() : (current.month as number) + 1 }-${ current.date.toString().length <= 1 ? '0' + current.date.toString() : current.date } ${getCurrentTime(current.h, current.m, current.s)}`, ), $Y: current.year, $M: current.month, $D: current.date, $H: current.h, $m: current.m, $s: current.s, $date: `${current.year}-${ current.month.toString().length <= 1 ? '0' + ((current.month as number) + 1).toString() : (current.month as number) + 1 }-${current.date.toString().length <= 1 ? '0' + current.date.toString() : current.date}`, $time: getCurrentTime(current.h, current.m, current.s), } as IDateDate; });
watch( () => selectDate.value, (o) => { emit('change', o); }, );
/** * 监听输入变化 更新日期并重新渲染 */ watch( () => value.value, (v: any) => { if (!checkDateFormat(v)) return;
cache.value = v;
let d = new Date(v); current.year = d.getFullYear(); current.month = d.getMonth(); current.date = d.getDate();
date.year = d.getFullYear(); date.month = d.getMonth(); date.date = d.getDate(); createCalendar(current.year, current.month); }, );
/** * 有值 hover时显示清楚按钮 */ const iconMouenterHandel = () => { if (unref(value)) { icon.value = 'icon-chacha'; } };
/** * 点击清楚 */ const iconClickHandel = () => { value.value = ''; clearAllHandel(); };
/** * 失去焦点 */ const BlurHandel = () => { if (!checkDateFormat(unref(value) as string)) { if (cache.value) { value.value = unref(cache); } else { value.value = ''; } } };
/** * 获取焦点 */ const FocusHandel = () => { isFoucus.value = true; (calendar.value as unknown as HTMLElement).style.animation = 'fade .3s forwards'; };
/** * 点击日历 天 */ const handleClick = (item: Idate) => { current.year = item.year; current.month = item.month; current.date = item.date; current.h = '23'; current.m = '59'; current.s = '59'; createCalendar(item.year, item.month); };
/** * 选择年 */ const clickCurrentYearHandel = (cur: number, index: number) => { isActiveLi.value = index; (date.year as number) = cur; createCalendar(date.year, date.month); };
/** * 选择月 */ const clickCurrentMonthHandel = (cur: number, index: number) => { isActiveLiM.value = index; (date.month as number) = cur - 1; createCalendar(date.year, date.month); };
/** * 清空 */ const clearAllHandel = () => { current.date = ''; current.h = 0; current.m = 0; current.s = 0; createCalendar(current.year, current.month); okHandel(); };
/** * 关闭 */ const close = () => { setTimeout(() => { (calendar.value as unknown as HTMLElement).style.animation = 'fade_up .4s forwards'; }, 150); setTimeout(() => { isFoucus.value = false; }, 500); };
/** * 点击今天 */ const currentTodayHandel = () => { initDate(); initTime(); okHandel(); };
/** * 点击确认 */ const okHandel = () => { close(); emit('ok', selectDate.value); value.value = selectDate.value?.$date; };
/** * 下一月 */ const plusMonth = () => { // 月数到12 加一年 if (date.month === 11) { date.month = 0; plusYear(false); } else { (date.month as number)++; } createCalendar(date.year, date.month); };
/** * 上一月 */ const minusMonth = () => { // 月数到0 减一年 if (date.month === 0) { date.month = 11; minusYear(false); } else { (date.month as number)--; } createCalendar(date.year, date.month); };
/** * 下一年 */ const plusYear = (create) => { if (date.year === 2099) { date.year = 1970; } else { (date.year as number)++; } if (create) { createCalendar(date.year, date.month); } };
/** * 上一年 */ const minusYear = (create) => { if (date.year == 1970) { date.year = 2099; } else { (date.year as number)--; } if (create) { createCalendar(date.year, date.month); } };
/** * 创建日历表 */ const createCalendar = (year: string | number, month: string | number) => { let d = new Date(); d.setFullYear(year as number); d.setMonth(month as number); d.setDate(1); let dayOfFirstDay = d.getDay(); days.value = [];
for (let i = 0; i < 42; i++) { d.setDate(1); d.setMonth(month as number); d.setDate(i - dayOfFirstDay + 1);
let isThisMonth = 1; if (d.getMonth() == month) { isThisMonth = 2; } else { isThisMonth = 1; }
if ( current.date == d.getDate() && current.month == d.getMonth() && current.year == d.getFullYear() ) { isThisMonth = 3; let date: Idate = { year: year, month: month, date: d.getDate(), isThisMonth: isThisMonth, }; days.value.push(date); } else { let date = { year: d.getFullYear(), month: d.getMonth(), date: d.getDate(), isThisMonth: isThisMonth, };
days.value.push(date); } } }; </script>
<style> /* stylelint-disable-next-line keyframes-name-pattern */ @keyframes fade_up { 0% { opacity: 1; }
100% { opacity: 0; } }
@keyframes fade { 0% { opacity: 0; }
100% { opacity: 1; } } </style>
<style scoped lang="less"> .calendar-card { @keyframes fade { 0% { opacity: 0; }
100% { opacity: 1; } }
position: absolute; z-index: 999; top: 40px; left: 50%; width: 310px; height: 293px; padding: 0 10px; transform: translateX(-50%); animation: fade 0.5s forwards; border: 1px solid #ebeef5; border-radius: 10px; background-color: #fff;
._button { position: relative; padding: 5px 8px; border-radius: 5px; cursor: pointer;
&:hover { background-color: #e7ebf5; }
.box_year, .box_month { position: absolute; z-index: 9; top: 18px; left: 50%; width: 114px; height: 159px; padding-top: 10px; transform: translateX(-50%); animation: fade 0.3s forwards;
ul { display: flex; flex-wrap: wrap; height: 156px; padding: 16px; border: 1px solid #d3d3dd; border-radius: 5px; background-color: #fff; box-shadow: 0 3px 7px 0 #ddd8d8;
li { width: 36px; height: 22px; margin-right: 5px; margin-bottom: 4px; color: #000; line-height: 22px; text-align: center;
&:hover { border-radius: 5px; background-color: #e7ebf5; }
&.active { border-radius: 5px; background-color: #1c67e5; color: #fff; // font-size: 12px; }
&:nth-child(2n) { margin-right: 0; } } } }
.box_month { width: 114px; height: 178px;
ul { height: 178px; } } }
> div { button { background-color: transparent !important; cursor: pointer;
&:hover { background-color: #e7ebf5; } } } }
.footer { margin-top: 5px; color: #000;
ul { display: flex; justify-content: end; margin-right: 10px; margin-bottom: 0;
li { margin-left: 10px; padding: 3px 8px; border: 1px solid #999; border-radius: 5px; font-size: 12px; cursor: pointer;
&:last-child { background-color: #1c67e5; color: #fff; } } } }
.calendar-bar { height: 40px; color: #727272; line-height: 40px; text-align: center;
/* background-color: #ebeef5; */ button { i { color: #333; } } }
.button { border: none; background-color: transparent; }
.button:hover { background-color: #eee; cursor: pointer; }
.ul-week { display: flex; justify-content: space-between; width: 100%; // margin: 5px 20px; margin: 0; margin: 8px 0; padding: 0 10px; // border-bottom: 1px solid #eee; color: #000; font-size: 13px; list-style: none; text-align: center; }
.li-week { display: inline-block; }
.ul-day { display: grid; grid-template-columns: repeat(7, 30px); grid-template-rows: repeat(6, 30px); align-items: start; justify-content: space-between; width: 100%; margin-bottom: 0; list-style: none; text-align: center; }
.li-day { display: inline-block; margin: 1px; border-radius: 5px; color: #000; font-size: 13px; line-height: 25px; text-align: center; }
.li-day:hover { background-color: #1c67e5; color: #fff; cursor: pointer; }
.li-day[isThisMonth='1'] { color: rgb(190 190 190); font-size: 13px; }
.li-day[isThisMonth='1']:hover { background-color: #1c67e5; color: #fff; font-size: 15px; cursor: pointer; }
.li-day[isThisMonth='3'] { border-radius: 5px; background-color: #1c67e5; color: rgb(255 255 255); font-weight: 600; } </style>
|