Vue3+vant4 二次封装“选择2个日期区间“的日历组件

前言

最近在开发H5酒店项目,但项目中有好几个地方都用到 “选择2个日期”的日历,各个页面的日历处理逻辑都一样,那就干脆封装一个,需要改时就直接在日历组件中改,方便后期维护和减少代码量。
版本号:vue3.3 + vant4

先看效果图

在这里插入图片描述

逻辑说明

1、在 src/components 下创建一个SecondaryCalendar.vue
2、通信方式
父组件:
引入子组件,通过props向子组件传入startTime 开始时间、endTime结束时间,时间格式为时间戳
例如:startTime:1717121848239,endTime: 1717378597104
子组件:
①、接收父传来的两个时间戳,由于props接收的值是只读的,所以将它们保存到新变量中,子组件就拿这两个变量进行逻辑操作
②、剩下的就是选中后时间格式化,最后整合我们要的数据到一个对象中,用emit自定义事件发送给父组件,父组件接收后直接给后端和页面展示,大概逻辑是这样,不难的。

完整代码

home.vue 父组件

<script lang="ts" setup>
import { onMounted, ref } from 'vue'
const showCalendar = ref(false) // 默认不显示日历
const startTim =ref( new Date().getTime())
const endTim = ref(new Date().getTime() + 24 * 60 * 60 * 1000)
const ruzhuDay_num = ref(1) // 入住天数
// 展示用的结束和开始时间
const client_startTim = ref('') 
const client_endTim = ref('') 

// 切换获取 Rili.vue 子组件传来的选中时间数据
const onConfirm = selectedValues => {
	console.log(selectedValues, 'selectedValues')

	let { sel_startTime, sel_endTime, show_startTime, show_endTime, ruzhu_day_num } = selectedValues
	startTime.value = sel_startTime // 用户选中的时间
	endTime.value = sel_endTime
	client_startTime.value = show_startTime // 页面展示的两个时间
	client_endTime.value = show_endTime
	ruzhuDay_num.value = ruzhu_day_num // 天数

	showCalendar.value = false // 拿到子组件传来的数据后隐藏日历组件
}

// 日历显/隐
const toggleRili = () => {
	showCalendar.value = !showCalendar.value
}

// 格式化选中日历的数据格式
// type 年月日或月日
// inputDateStr  时间戳
const formatDate = (type, inputDateStr) => {
	// 创建Date对象
	let date = new Date(inputDateStr)

	// 获取年、月、日
	let year = date.getFullYear()
	let month = date.getMonth() + 1 // getMonth返回的月份是从0开始的,所以需要加1
	let day = date.getDate()

	// 格式化月份和日期,保证始终为两位数
	month = month < 10 ? '0' + month : month
	day = day < 10 ? '0' + day : day

	if (type == 'ymd') {
		// 返回格式化后的日期字符串
		return year + '年' + month + '月' + day + '日'
	} else if (type == 'md') {
		// 返回格式化后的日期字符串
		return month + '月' + day + '日'
	}
}

onMounted(() => {
	// 默认日历组件 展示今天和明天的日期
	client_startTime.value = formatDate('md', new Date().getTime())
	client_endTime.value = formatDate('md', new Date().getTime() + 24 * 60 * 60 * 1000)
})


</script>
	
</template>
	<div>
		<van-button type="primary" @click="toggleRili">点击显示</van-button>
		<div>入住{{ client_startTime }} ------   共{{ ruzhuDay_num }}晚 -------  离店{{ client_endTime }}  </div>
		<!-- 日历组件 -->
		<SecondaryCalendar :show-calendar="showCalendar" 
		      :start-time="startTime" 
			  :end-time="endTime" 
			  @confirm="onConfirm" 
			  @changeValue="showCalendar = false" 
		/>
	</div>
</template>

SecondaryCalendar.vue 二次封装的日历子组件

<script lang="ts">
// 日历组件
export default {
	name: 'Rili',
}
</script>
<script lang="ts" setup>
import { ref, computed, onMounted, PropType, reactive, toRefs, watch } from 'vue'

const props = defineProps({
	// 两个时间接收的都是时间戳,例如 1717121848239
	// 开始时间
	startTime: {
		type: [String, Number] as PropType<string | number>,
		default: new Date().getTime(),
	},
	// 结束时间
	endTime: {
		type: [String, Number] as PropType<string | number>,
		default: new Date().getTime() + 24 * 60 * 60 * 1000,
	},
	// 传入的显影状态
	showCalendar: {
		type: Boolean,
	},
})

//定义要向父组件传递的事件
const emit = defineEmits(['changeValue', 'confirm'])

const useShowList = () => {
	const state = reactive({
		local_rili_startTime: props.startTime, // 复制一份传入的时间用于操作
		local_rili_endTime: props.endTime,
		show_rili_one: new Date().getTime(), // 入住月日
		show_rili_two: new Date().getTime() + 24 * 60 * 60 * 1000, // 离店月日
		ruzhu_day_num: 0, // 入住天数
	})
	return toRefs(state)
}
const { show_rili_one, show_rili_two, ruzhu_day_num, local_rili_startTime, local_rili_endTime } = useShowList()

// 是否显示日历   将父组件传来的值存起来,用于控制日历的显/隐
const isShowBottom = computed(() => props.showCalendar)

// 格式化日历数据格式
const formatter = day => {
	if (day.type === 'start') {
		day.bottomInfo = '入住'
	} else if (day.type === 'end') {
		day.bottomInfo = '离店'
	}
	return day
}
// 确认日历
const onConfirm = dates => {
	emit('changeValue')
	// console.log(dates, 'dates')

	// // 格式化四个数据
	local_rili_startTime.value = formatDate('ymd', dates[0])
	local_rili_endTime.value = formatDate('ymd', dates[1])
	show_rili_one.value = formatDate('md', dates[0])
	show_rili_two.value = formatDate('md', dates[1])

	// console.log(local_rili_startTime.value, 'local_rili_startTime')
	// console.log(local_rili_endTime.value, 'local_rili_endTime')

	// 防止默认直接选择时日历不显示几晚的bug
	onSelect(dates)
	let selectData = {
		// 用户选的完整 开始和结束时间
		sel_startTime: local_rili_startTime.value,
		sel_endTime: local_rili_endTime.value,
		// 页面展示 开始和结束时间
		show_startTime: show_rili_one.value,
		show_endTime: show_rili_two.value,
		ruzhu_day_num: ruzhu_day_num.value, // 选中天数
	}
	emit('confirm', selectData)
}
// 触发自身暴露出去的改变日历显/隐方法
const onClose = () => {
	emit('changeValue')
}
// 格式化选中日历的数据格式
// type 年月日或月日
const formatDate = (type, inputDateStr) => {
	// 创建Date对象
	let date = new Date(inputDateStr)

	// 获取年、月、日
	let year = date.getFullYear()
	let month = date.getMonth() + 1 // getMonth返回的月份是从0开始的,所以需要加1
	let day = date.getDate()

	// 格式化月份和日期,保证始终为两位数
	month = month < 10 ? '0' + month : month
	day = day < 10 ? '0' + day : day

	if (type == 'ymd') {
		// 返回格式化后的日期字符串
		return year + '年' + month + '月' + day + '日'
	} else if (type == 'md') {
		// 返回格式化后的日期字符串
		return month + '月' + day + '日'
	}
}

// 计算入住天数
const onSelect = dates => {
	if (dates.length > 1) {
		// 将日期字符串转换为Date对象
		const date1 = new Date(dates[0])
		const date2 = new Date(dates[dates.length - 1])

		// 计算时间差,单位为毫秒
		const diff = date2 - date1

		// 将毫秒转换为天数
		ruzhu_day_num.value = diff / (1000 * 60 * 60 * 24)

		console.log(`选择的日期间隔为${ruzhu_day_num.value}`)
	}
}
onMounted(() => {
	local_rili_startTime.value = formatDate('ymd', local_rili_startTime.value)
	local_rili_endTime.value = formatDate('ymd', local_rili_endTime.value)
	show_rili_one.value = formatDate('md', show_rili_one.value)
	show_rili_two.value = formatDate('md', show_rili_two.value)
})
</script>
<template>
	<!-- 日历 -->
	<van-calendar
		v-model:show="isShowBottom"
		type="range"
		title="请选择入住离店日期"
		:formatter="formatter"
		safe-area-inset-bottom
		@confirm="onConfirm"
		@select="onSelect"
		@close="onClose"
	/>
</template>
<style scoped lang="scss"></style>

相关推荐

  1. 如何封装一个Vue3组件库?

    2024-06-06 12:32:05       50 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-06 12:32:05       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-06 12:32:05       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-06 12:32:05       82 阅读
  4. Python语言-面向对象

    2024-06-06 12:32:05       91 阅读

热门阅读

  1. uni-app(优医咨询)项目实战 - 第7天

    2024-06-06 12:32:05       24 阅读
  2. 如何选择适合自己的大模型

    2024-06-06 12:32:05       37 阅读
  3. Flutter StatefulWidget 和 StatelessWidget 的区别

    2024-06-06 12:32:05       34 阅读
  4. Python数组存放变量:深入探索与实用技巧

    2024-06-06 12:32:05       36 阅读
  5. 深度解读CHATGPT基本原理

    2024-06-06 12:32:05       31 阅读
  6. qemu虚拟机安装麒麟v10 arm版系统

    2024-06-06 12:32:05       27 阅读
  7. 微信小程序长图片自适应

    2024-06-06 12:32:05       23 阅读
  8. 算法——二分查找

    2024-06-06 12:32:05       32 阅读
  9. PHPstudy情况下上传图片马需要的.htaccess文件

    2024-06-06 12:32:05       30 阅读
  10. Redis命令实践

    2024-06-06 12:32:05       24 阅读