效果图
一、使用方法
<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',
maxDate: '2023/11/11',
dateNum: 2,
minRange: 0,
startText: '开始',
endText: '结束',
},
value: [],
show: false,
}
},
methods: {
open(){
this.show = true;
},
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>