vue3中封装类似bootstrap的快捷时间选择插件

封装快捷时间选择

效果图如下
选择月份
选择时间
选择具体时间
已实现上下年、上下月、选择时间、手动输入等操作;
具体代码如下:

<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>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值