效果图

请添加图片描述

一、使用方法

<template>
    <view>
        <view class="" @click="open">
        	<text>展示日历{{value[0]}}-{{value[1]}}</text>
        </view>
        <calendar v-if="show" @change="change" @close="close" :show.sync="show" v-model="value" :option="dateOption"></calendar>
    </view>
</template>

<script>
    export default {
        data() {
        	return {
        		dateOption: {
        			formatter:'yyyy/mm/dd',//默认为时间戳
        			minDate:'2023/01/05', //默认为当前日期往前6个月
        			maxDate: '2023/11/11',//默认为当前日期往后6个月
        			dateNum: 2, //选择的日期个数,可选值1,2, 默认为2
        			minRange: 0, //两个日期的间隔天数,默认为0
        			startText: '开始', //第一个日期的文字显示,默认‘开始’
        			endText: '结束',//第一个日期的文字显示,默认‘结束’
        		},
        		value: [],
        		show: false,
        	}
        },
		methods: {
			open(){
				this.show = true;
			},
			// value值发生变更,可选
			change(){},
			// 关闭日历事件触发,可选
			close(){}
		}
    }
</script>

二、组件编写,两个文件、直接上代码

month.vue

<template>
	<view class="month-box">

		<view class="box-title">
			<text class="titletext">{{year}}{{month}}</text>
		</view>
		<view class="month">
			<view class="item" v-for="item in days">
				<view @click="selectDay(item)"
					:class="{'item-con': true, 'itemno': !item.isSelect, 'item-sel': (item.timeNo==value[0] || item.timeNo==value[1]),  'item-jian': (item.timeNo>value[0] && item.timeNo<value[1])}"
					v-if="item">
					<text class="text">{{item.date}}</text>
					<text class="kai" v-if="item.timeNo==value[0]">{{startText}}</text>
					<text class="jie" v-if="item.timeNo==value[1]">{{endText}}</text>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		props: {
			year: {
				type: String | Number,
				default: '2023'
			},
			startText: {
				type: String,
				default: '开始'
			},
			endText: {
				type: String,
				default: '结束'
			},
			month: {
				type: String | Number,
				default: '4'
			},
			maxDate: {
				type: Boolean | Number,
				default: false
			},
			minDate: {
				type: Boolean | Number,
				default: false
			},
			value: {
				type: Array,
				default: () => {
					return []
				}
			}
		},
		data() {
			return {
				day: 24 * 3600 * 1000,
				max: 0,
				min: new Date(this.year + '/' + this.month).getTime(),
			}
		},
		methods: {
			selectDay(day) {
				if(day.isSelect){
					this.$emit('change', day.timeNo)
				}
			},
		},
		computed: {
			days() {
				let dataArr = [];
				if(new Date(this.min).getDay() == 0){
					dataArr[5] = undefined;
				} else if(new Date(this.min).getDay()>=2){
					dataArr[new Date(this.min).getDay()-2] = undefined;
				}
				if (this.month == 12) {
					this.max = new Date((this.year - 0 + 1) + '/1').getTime()
				} else {
					this.max = new Date(this.year + '/' + (this.month - 0 + 1)).getTime()
				}
				let i = 0;
				let isSelect = true;
				while (this.min + i * this.day < this.max) {
					if (this.maxDate && this.min + i * this.day > this.maxDate) {
						dataArr.push({
							date: i + 1,
							isSelect: false,
							timeNo: this.min + i * this.day
						})
					} else if (this.minDate && this.min + i * this.day < this.minDate) {
						dataArr.push({
							date: i + 1,
							isSelect: false,
							timeNo: this.min + i * this.day
						})
					} else {
						dataArr.push({
							date: i + 1,
							isSelect: true,
							timeNo: this.min + i * this.day
						})
					}
					i++
				}
				return dataArr;
			}
		}
	}
</script>

<style scoped lang="less">
	.box-title {
		display: flex;
		align-items: center;
		justify-content: center;

		.titletext {
			color: #000;
			padding-top: 30rpx;
			font-size: 32rpx;
			font-weight: bold;
			padding-bottom: 20rpx;
		}
	}

	.month {
		padding: 0 25rpx;
		display: flex;
		flex-flow: row;
		flex-wrap: wrap;

		.item {
			width: 100rpx;
			height: 100rpx;

			.item-con {
				width: 100rpx;
				height: 100rpx;
				color: #333;
				display: flex;
				flex-flow: column;
				justify-content: center;
				align-items: center;

				&.itemno {
					color: #999;
				}

				&.item-sel {
					background-color: rgb(60, 156, 255);

					.text {
						color: white;
					}

					.kai {
						font-size: 20rpx;
						line-height: 1;
						color: white;
					}

					.jie {
						font-size: 20rpx;
						line-height: 1;
						color: white;
					}
				}

				&.item-jian {
					background-color: rgb(236, 245, 255);

					.text {
						color: rgb(60, 156, 255);
					}
				}
			}
		}
	}
</style>

calendar.vue

<template>
	<view class="calendar" v-if="show">
		<view class="bg" @click="close">

		</view>
		<view class="con">
			<view class="con-top">
				<view class="con-title">
					<view class="con-clear" @click="clear">
						<text class="textclear">清除</text>
					</view>
					<view class="title">
						<text class="texttitle">日期选择</text>
					</view>
					<view class="con-cancle" @click="close">
						<text class="cancle">关闭</text>
					</view>
				</view>
				
				<view class="date">
					<text v-for="i in zhou">{{i}}</text>
				</view>
			</view>
			<view class="con-center">
				<scroll-view class="scroll" scroll-y="true" :scroll-into-view="toview" scroll-top="120">
					<month @change="change" :startText="option.startText" :endText="option.endText" :id="'a'+item.year+item.month" v-for="item in monthArr"
						:maxDate="option.maxDate" :minDate="option.minDate" :value="selectValue" :month="item.month"
						:year="item.year"></month>
				</scroll-view>
			</view>
			<view class="con-button" @click="sureDate" :class="{on: isClick}">
				<text class="sure">确定</text>
			</view>
		</view>
	</view>
</template>

<script>
	import month from "./month.vue"
	export default {
		props: {
			show: {
				type: Boolean,
				default: false
			},
			option: {
				type: Object,
				default: {}
			},
			value: {
				type: Array,
				default: []
			}
		},
		components: {
			month
		},
		computed: {
			isClick(){
				return (this.selectValue.length == this.option.dateNum) || this.selectValue.length == 2
			}
		},
		data() {
			let value = [];
			if (this.value) {
				if (this.value[0]) {
					value[0] = new Date(new Date(this.value[0]).toDateString()).getTime();
				}
				if (this.value[1]) {
					value[1] = new Date(new Date(this.value[1]).toDateString()).getTime()
				}
			}
			return {
				toview: '',
				zhou: ['一', '二', '三', '四', '五', '六', '日'],
				monthArr: [],
				selectValue: value,
				isShow: false
			}
		},
		mounted() {
			this.option.maxDate = new Date(this.option.maxDate).getTime() || new Date().getTime() + 183 * 24 * 3600 * 1000
			this.option.minDate = new Date(this.option.minDate).getTime() || new Date().getTime() - 183 * 24 * 3600 * 1000
			let maxYear = new Date(this.option.maxDate).getFullYear();
			let maxMonth = new Date(this.option.maxDate).getMonth() + 1;
			let minYear = new Date(this.option.minDate).getFullYear();
			let minMonth = new Date(this.option.minDate).getMonth()+1;
			let i = 0;
			let monthArr = [];
			while (new Date(new Date(minYear + '/' + minMonth).setMonth(minMonth - 1 + i)).getTime() <= new Date(maxYear +
					'/' + maxMonth).getTime()) {
				monthArr.push({
					year: new Date(new Date(minYear + '/' + minMonth).setMonth(minMonth - 1 + i)).getFullYear(),
					month: new Date(new Date(minYear + '/' + minMonth).setMonth(minMonth - 1 + i)).getMonth() + 1
				});
				i++;
			}
			this.monthArr = monthArr;
			let toview = '';
			if (this.selectValue[0]) {
				toview = 'a' + new Date(this.selectValue[0]).getFullYear() + (new Date(this.selectValue[0]).getMonth() + 1)
			}
			this.$nextTick(() => {
				this.toview = toview
			})
		},
		methods: {
			formatter(date, formatter){
				let year = new Date(date).getFullYear()
				let month = new Date(date).getMonth()+1
				let day = new Date(date).getDate()
				return formatter.replace(/yyyy/i, year).replace(/mm/i, month).replace(/dd/i, day)
			},
			sureDate(){
				if(!this.isClick) return
				let value = [];
				if(this.option.formatter){
					value = [this.formatter(this.selectValue[0], this.option.formatter)]
					if(this.selectValue[1]){
						value.push(this.formatter(this.selectValue[1], this.option.formatter))
					}
				}
				this.$emit('input', value)
				this.$emit('update:show', false)
				this.$emit('change')
				this.$emit('close')
			},
			close(){
				this.$emit('update:show', false)
				this.$emit('close')
			},
			clear(){
				this.$emit('input', [])
				this.$emit('update:show', false)
				this.$emit('change', [])
				this.$emit('close')
			},
			change(day) {
				if(this.option.dateNum && this.option.dateNum == 1){
					this.selectValue = [day];
					return;
				}
				if (!this.selectValue.length) {
					this.selectValue = [day];
				} else if (this.selectValue.length == 1) {
					if (this.selectValue[0] > day) {
						this.selectValue = [day]
					} else {
						if (this.option.minRange) {
							if (this.option.minRange * 3600 * 24 * 1000 + this.selectValue[0] > day) {
								uni.showToast({
									icon: 'none',
									title: `选择间隔时间必须大于${this.option.minRange}`
								})
							} else {
								this.selectValue = [this.selectValue[0], day];
							}
						} else {
							this.selectValue = [this.selectValue[0], day];
						}
					}
				} else {
					this.selectValue = [day]
				}
			}
		}
	}
</script>

<style scoped lang="less">
	.calendar {
		position: fixed;
		bottom: 0;
		left: 0;
		right: 0;
		top: 0;
		height: 100vh;

		.bg {
			width: 750rpx;
			height: 100vh;
			background: rgba(0, 0, 0, 0.5);
		}

		.con {
			position: absolute;
			left: 0;
			bottom: 0;
			right: 0;
			background: #fff;
			padding-top: 20rpx;

			.con-title {
				display: flex;
				height: 80rpx;
				flex-flow: row;
				justify-content: space-between;
				align-items: center;
				padding: 0 40rpx;
				padding-bottom: 20rpx;
			}

			.date {
				display: flex;
				flex-flow: row;
				height: 60rpx;
				justify-content: space-between;
				align-items: center;
				padding: 0 40rpx;
				border-bottom: 2rpx solid #eee;
			}

			.con-center {
				height: 800rpx;
				padding: 20rpx 0;
				.scroll {
					height: 800rpx;

					.item {
						height: 500rpx;
					}
				}
			}
			.con-button{
				height: 80rpx;
				width: 650rpx;
				display: flex;
				align-items: center;
				justify-content: center;
				margin: 20rpx 50rpx;
				background-color: rgb(60, 156, 255);
				border-radius: 40rpx;
				opacity: .5;
				&.on{
					opacity: 1;
				}
				.sure{
					font-size: 40rpx;
					color: #fff;
				}
			}
		}
	}
</style>
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐