封装快捷时间选择
效果图如下
已实现上下年、上下月、选择时间、手动输入等操作;
具体代码如下:
<template>
<el-popover
:disabled="disabled"
:width="dataState.showMinutes ? 650 : 400"
transition="null"
trigger="click"
@show="initModal"
:fallback-placements="['bottom', 'top']"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
>
<template #reference>
<el-input
ref="buttonRef"
v-model="emitValue"
:class="showInput ? '' : 'opacity'"
:disabled="disabled"
:placeholder="placeholder"
clearable
@clear="clearValue"
@input="changeInput"
:maxlength="type === 'date' ? 10 : 16"
>
<template #append>
<el-icon v-if="type === 'datetime'">
<Clock />
</el-icon>
<el-icon v-else>
<Calendar />
</el-icon>
</template>
</el-input>
</template>
<div class="rili">
<div className="rili-container">
<div class="handle-title">
<div class="change-btn">
<el-link class="icon" :underline="false" @click="preYear(new Date(dataState.currentYear, 0))">
<el-icon> <DArrowLeft /> </el-icon>
</el-link>
<el-link
:underline="false"
class="icon"
@click="preMonth(new Date(dataState.currentYear, dataState.currentMonth))"
>
<el-icon> <ArrowLeft /> </el-icon>
</el-link>
</div>
<span>{{ fullName }}</span>
<div class="change-btn">
<el-link
class="iocn"
:underline="false"
@click="nextMonth(new Date(dataState.currentYear, dataState.currentMonth))"
>
<el-icon><ArrowRight /></el-icon>
</el-link>
<el-link class="iocn" :underline="false" @click="nextYear(new Date(dataState.currentYear, 0))">
<el-icon><DArrowRight /></el-icon>
</el-link>
</div>
</div>
<template v-if="dataState.showDate">
<div className="rili-title">
<template v-for="(v, i) in dataState.week" :key="i">
<span class="week-name"> {{ v }} </span>
</template>
</div>
<div class="rili-box">
<span class="rili-month">{{ dataState.currentMonth + 1 }}</span>
<template v-for="(v, i) in dataState.resDays" :key="i">
<div className="rili-list-tr">
<template v-for="(item, index) in v" :key="index">
<el-link
:underline="false"
@click="handleListClick(item)"
:class="[
'day-name',
item.type == 'current' && !item.disabled ? '' : 'day-other'
]"
:disabled="item.disabled"
>
<span
:class="[
'normal-txt',
dataState.chooseDate &&
dataState.chooseDate.year === item.year &&
dataState.chooseDate.month === item.month &&
dataState.chooseDate.date === item.date
? 'active-day'
: '',
item.disabled ? 'disable-day' : ''
]"
>
{{ item.date }}
</span>
</el-link>
</template>
</div>
</template>
</div>
</template>
<template v-if="dataState.showHours && type === 'datetime'">
<div class="hour-box">
<template v-for="(v, i) in hoursArr" :key="i">
<el-link
:underline="false"
:disabled="computHour && dataState.nowTime.hour < v"
:class="[
'rili-hour',
dataState.chooseHour && dataState.chooseHour === v ? 'current-hour' : '',
computHour && dataState.nowTime.hour < v ? 'disable-hour' : ''
]"
@click="handleHours(v)"
>
{{ v + ':00' }}
</el-link>
</template>
</div>
</template>
<template v-if="dataState.showMinutes && type === 'datetime'">
<div class="hour-box">
<template v-for="(v, i) in minutesArr" :key="i">
<el-link
:underline="false"
:disabled="
computHour &&
dataState.chooseHour == dataState.nowTime.hour &&
dataState.nowTime.minute < v
"
:class="[
'rili-hour',
'rili-hour-1',
dataState.chooseMinute && dataState.chooseMinute === v ? 'current-hour' : '',
computHour &&
dataState.chooseHour == dataState.nowTime.hour &&
dataState.nowTime.minute < v
? 'disable-hour'
: ''
]"
@click="handleMinutes(v)"
>
{{ `${dataState.chooseHour}:${v}` }}
</el-link>
</template>
</div>
</template>
</div>
</div>
</el-popover>
</template>
<script lang="ts" setup>
import { reactive, toRefs, ref, onMounted, watch, computed, unref } from 'vue'
import DatePickerContent from './DatePickerContent.vue'
import { ArrowLeft, ArrowRight, DArrowLeft, DArrowRight, Clock, Calendar } from '@element-plus/icons-vue'
const props = defineProps({
value: {
type: String,
default: ''
},
showInput: {
type: Boolean,
default: true
},
type: {
type: String,
default: 'datetime'
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
},
imposeTime: {
type: Boolean,
default: true
},
minTime: {
type: String,
default: '1999-12-31 23:59'
}
})
const buttonRef = ref()
const visible = ref(false)
const getAllTime = (data: any) => {
return data < 10 ? `0${data}` : `${data}`
}
const hoursArr = (() => {
let arr: any = []
for (let i = 0; i < 24; i++) {
arr.push(getAllTime(i))
}
return arr
})()
const minutesArr = (() => {
let arr: any = []
for (let i = 0; i < 60; i++) {
arr.push(getAllTime(i))
}
return arr
})()
const dataState: any = reactive({
showDate: true, // 显示选择日期
showHours: false, // 显示选择时间
showMinutes: false, // 显示选择分钟
weekList: [], // 周日历列表
chooseDate: null, // 选中的起始日期
chooseHour: null,
chooseMinute: null,
currentYear: new Date().getFullYear(),
currentMonth: new Date().getMonth(), // 日历中要显示的月份
week: ['日', '一', '二', '三', '四', '五', '六'],
currentDay: new Date().getDate(), // 今天是当月几号
lastDay: new Date(new Date().getTime() - 24 * 60 * 60 * 1000).getDate(), // 昨天是当月几号
resDays: [], // 二维数组
nowTime: null // 当前时间
})
// 获取当月第一天是周几
const getCurrentFirstWeekDay = (date: any) => {
let currentFirstWeekDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay()
return currentFirstWeekDay
}
// 获取本月一共有多少天
const getMonthAllDay = (date: any) => {
let allDays = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()
return allDays
}
// 获取上个月显示天数
const getPrevMonthDay = (date: any) => {
let days = getCurrentFirstWeekDay(date) // 上个月要显示几天
let day = new Date(date.getFullYear(), date.getMonth(), 0).getDate() // 获取上个月一共有多少天
let prevDays = []
for (let i = 0; i < days; i++) {
let obj = {
date: day--,
year: date.getMonth() === 0 ? date.getFullYear() - 1 : date.getFullYear(),
month: date.getMonth() === 0 ? 12 : date.getMonth(),
type: 'prev'
}
let choose_time = new Date(`${obj.year}-${obj.month}-${obj.date}`).getTime()
prevDays.push({
...obj,
disabled:
new Date().getTime() < choose_time ||
(props.imposeTime && choose_time < new Date(props.minTime).getTime())
})
}
return prevDays.sort((a, b) => a.date - b.date)
}
// 获取当前月显示天数
const getNowMonthDay = (date: any) => {
let showDays = []
let day = 1
let allDays = getMonthAllDay(date)
for (let i = 0; i < allDays; i++) {
let obj = {
date: day++,
year: date.getFullYear(),
month: date.getMonth() + 1,
type: 'current'
}
let choose_time = new Date(`${obj.year}-${obj.month}-${obj.date}`).getTime()
showDays.push({
...obj,
disabled:
new Date().getTime() < choose_time ||
(props.imposeTime && choose_time < new Date(props.minTime).getTime())
})
}
return showDays
}
// 获取下个月显示的天数
const getNextMonthDay = (date: any) => {
let currentFirstWeekDay = getCurrentFirstWeekDay(date) // 当前月1号是星期几
let allDays = getMonthAllDay(date) // 当前月总天数
let days = 35 - currentFirstWeekDay - allDays // 下个月要显示的天数
let day = 1
let nextDays = []
for (let i = 0; i < days; i++) {
let obj = {
date: day++,
year: date.getMonth() === 11 ? date.getFullYear() + 1 : date.getFullYear(),
month: date.getMonth() === 11 ? 1 : date.getMonth() + 2,
type: 'next'
}
let choose_time = new Date(`${obj.year}-${obj.month}-${obj.date}`).getTime()
nextDays.push({
...obj,
disabled:
new Date().getTime() < choose_time ||
(props.imposeTime && choose_time < new Date(props.minTime).getTime())
})
}
return nextDays
}
// 传入展示天数的原数组,转换成二维数组
const handleFormatDates = (arr: any) => {
const arr2 = []
// 7列,一个月最多会显示5行
for (let i = 0; i < 5; i++) {
const temp = arr.slice(i * 7, i * 7 + 7)
arr2.push(temp)
}
return arr2
}
// 操作函数(上个月)
const preMonth = (date: any) => {
let newDate = null
if (props.imposeTime && date.getTime() < new Date(props.minTime).getTime()) {
return
}
if (date.getMonth() === 0) {
newDate = new Date(date.getFullYear() - 1, 11)
dataState.currentMonth = 11
dataState.currentYear = date.getFullYear() - 1
} else {
newDate = new Date(date.getFullYear(), date.getMonth() - 1)
dataState.currentMonth = date.getMonth() - 1
dataState.currentYear = date.getFullYear()
}
handleInitData(newDate)
}
// 操作函数(下个月)
const nextMonth = (date: any) => {
let newDate = null
if (date.getMonth() === 11) {
newDate = new Date(date.getFullYear() + 1, 0)
dataState.currentMonth = 0
dataState.currentYear = date.getFullYear() + 1
} else {
newDate = new Date(date.getFullYear(), date.getMonth() + 1)
dataState.currentMonth = date.getMonth() + 1
dataState.currentYear = date.getFullYear()
}
handleInitData(newDate)
}
// 操作函数(上年)
const preYear = (date: any) => {
let newDate = new Date(date.getFullYear() - 1, 0)
if (props.imposeTime && newDate.getTime() < new Date(props.minTime).getTime()) {
return
}
dataState.currentYear = newDate.getFullYear()
handleInitData(newDate)
}
// 操作函数(下年)
const nextYear = (date: any) => {
let newDate = new Date(date.getFullYear() + 1, 0)
dataState.currentYear = newDate.getFullYear()
handleInitData(newDate)
}
// 初始化日历列表数据
const handleInitData = (date: any) => {
let prevDays = getPrevMonthDay(date)
let showDays = getNowMonthDay(date)
let nextDays = getNextMonthDay(date)
let newDays = []
newDays.push(...prevDays, ...showDays, ...nextDays)
let resDays = handleFormatDates(newDays)
dataState.resDays = resDays
let now_time = new Date()
let obj: any = {
year: now_time.getFullYear(),
month: now_time.getMonth() + 1,
day: now_time.getDate(),
hour: now_time.getHours(),
minute: now_time.getMinutes()
}
dataState.nowTime = obj
}
// 隐藏popover
const hidePop = () => {
buttonRef?.value.input.click()
}
const changeList: any = []
// 点击日期
const handleListClick = (item: any) => {
if (item.disabled) {
return
}
dataState.chooseDate = { ...item }
if (props.type !== 'datetime') {
hidePop()
emtisFn()
} else {
dataState.showDate = false
dataState.showHours = true
}
}
// 点击时间
const handleHours = (item: any) => {
dataState.chooseHour = item
dataState.showHours = false
dataState.showMinutes = true
}
// 点击分钟
const handleMinutes = (item: any) => {
dataState.chooseMinute = item
visible.value = false
emtisFn()
hidePop()
}
// 初始化选择时间
const initChooseTime = (time: any) => {
dataState.currentYear = new Date(time).getFullYear()
dataState.currentMonth = new Date(time).getMonth()
dataState.chooseDate = {
type: 'current',
date: new Date(time).getDate(),
year: new Date(time).getFullYear(),
month: new Date(time).getMonth() + 1
}
dataState.chooseHour = getAllTime(new Date(time).getHours())
dataState.chooseMinute = getAllTime(new Date(time).getMinutes())
}
// 重置选择时间
const resetChooseTime = () => {
dataState.chooseDate = null
dataState.chooseHour = ''
dataState.chooseMinute = ''
dataState.currentYear = new Date().getFullYear()
dataState.currentMonth = new Date().getMonth()
}
// 点击输入框展示modal
const initModal = () => {
visible.value = !visible.value
dataState.showDate = true
dataState.showHours = false
dataState.showMinutes = false
if (visible.value) {
if (props.value) {
initChooseTime(props.value)
handleInitData(new Date(props.value))
} else {
handleInitData(new Date())
resetChooseTime()
}
}
}
// emits函数
const emtisFn = () => {
const { year, date, month } = dataState.chooseDate
let day = `${year}-${getAllTime(month)}-${getAllTime(date)}`
let timer = `${dataState.chooseHour}:${dataState.chooseMinute}`
let t = props.type === 'datetime' ? `${day} ${timer}` : `${day}`
emits('update:value', t)
emits('change', t)
}
// 自动计算title
const fullName = computed(() => {
let name = ''
let { showDate, showHours, showMinutes, chooseDate, chooseHour, currentYear, currentMonth } = dataState
currentMonth = getAllTime(currentMonth + 1)
let dates = getAllTime(chooseDate?.date)
if (showDate) {
name = `${currentYear}年${currentMonth}月`
}
if (showHours) {
name = `${currentYear}年${currentMonth}月${chooseDate?.date}日`
}
if (showMinutes) {
name = `${currentYear}年${currentMonth}月${chooseDate?.date}日 ${chooseHour}:00`
}
return name
})
// 自动计算分钟是否大于当前时间
const computHour = computed(() => {
let flag =
dataState.chooseDate &&
dataState.chooseDate.year == dataState.nowTime.year &&
dataState.chooseDate.month == dataState.nowTime.month &&
dataState.chooseDate.date == dataState.nowTime.day
return flag
})
const changeInput = (t: any) => {
console.log(213, t)
let timer = t
if ((props.type === 'date' && t.length === 10) || (props.type === 'datetime' && t.length === 16)) {
if (props.imposeTime) {
if (new Date(t).getTime() < new Date(props.minTime).getTime()) {
timer = props.type === 'date' ? '2000-01-01' : '2000-01-01 00:00'
}
}
if (new Date(t).getTime() > new Date().getTime()) {
timer =
props.type === 'date'
? `${dataState.nowTime?.year}-${getAllTime(dataState.nowTime?.month)}-${getAllTime(
dataState.nowTime?.day
)}`
: `${dataState.nowTime?.year}-${getAllTime(dataState.nowTime?.month)}-${getAllTime(
dataState.nowTime?.day
)} ${getAllTime(dataState.nowTime?.hour)}:${getAllTime(dataState.nowTime?.minute)}`
}
initChooseTime(timer)
handleInitData(new Date(timer))
emits('update:value', timer)
emits('change', timer)
}
if (t.length === 0) {
emits('update:value', '')
emits('change', '')
}
// emits('update:value', timer)
// emits('change', timer)
}
const clearValue = () => {
emits('update:value', '')
}
onMounted(() => {
handleInitData(new Date())
})
const emits = defineEmits(['change', 'look', 'timeline', 'archives', 'update:value'])
//重新定义
const emitValue = computed({
get: () => props.value,
set: (value) => emits('update:value', value)
})
defineExpose({
hidePop
})
</script>
<style scoped lang="scss">
.rili {
width: 100%;
display: flex;
.rili-container {
width: 100%;
display: flex;
flex-direction: column;
box-shadow: 0px 2px 6px 1px rgba(48, 60, 82, 0.1);
.handle-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.rili-title {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.week-name {
width: calc(100% / 7);
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 600;
color: #606266;
line-height: 22px;
}
}
.rili-box {
width: 100%;
height: 275px;
overflow: hidden;
background: #ffffff;
position: relative;
}
.rili-month {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
font-family: PingFang SC-Semibold, PingFang SC;
font-weight: 600;
color: #f7f8fa;
}
.rili-list-tr {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 16px;
position: relative;
z-index: 12;
.day-name {
width: calc(100% / 7);
height: 36px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #606266;
line-height: 22px;
position: relative;
}
.day-other {
color: #999;
}
}
.normal-txt {
position: relative;
z-index: 12;
font-size: 14px;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 500;
line-height: 22px;
display: flex;
justify-content: center;
align-items: center;
// height: 100%;
}
.current-day {
color: 2a79f1;
}
.disable-day {
// background-color: #c0c4cc;
color: #c0c4cc;
// width: 100%;
// height: 100%;
}
.active-day {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: #2a79f1 !important;
color: #ffffff !important;
position: relative;
z-index: 12;
}
.hour-box {
display: flex;
flex-wrap: wrap;
}
.rili-hour {
width: 25%;
text-align: center;
height: 40px;
line-height: 40px;
color: #606266;
font-size: 14px;
font-weight: 400;
border-radius: 5px;
}
.disable-hour {
color: #c0c4cc;
}
.rili-hour-1 {
width: 10%;
}
.current-hour {
background: #2a79f1;
color: #fff;
}
}
}
.change-btn {
padding: 6px 10px;
.icon {
margin-right: 5px;
}
.iocn {
margin-left: 5px;
}
}
:deep(.el-input-group__append) {
padding: 0 8px !important;
background: #fff !important;
}
.opacity {
opacity: 0;
}
</style>