在uni-datetime-picker组件中
calendar.vue
<template><view class="uni-calendar" @mouseleave="leaveCale"><view v-if="!insert && show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"@click="maskClick"></view><view v-if="insert || show" class="uni-calendar__content":class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}"><view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}"><view class="uni-calendar__header-btn-box" @click.stop="changeMonth('pre')"><view class="uni-calendar__header-btn uni-calendar--left"></view></view><picker mode="date" :value="date" fields="month" @change="bindDateChange"><textclass="uni-calendar__header-text">{{ (nowDate.year||'') + yearText + ( nowDate.month||'') + monthText}}</text></picker><view class="uni-calendar__header-btn-box" @click.stop="changeMonth('next')"><view class="uni-calendar__header-btn uni-calendar--right"></view></view><view v-if="!insert" class="dialog-close" @click="close"><view class="dialog-close-plus" data-id="close"></view><view class="dialog-close-plus dialog-close-rotate" data-id="close"></view></view></view><view class="uni-calendar__box"><view v-if="showMonth" class="uni-calendar__box-bg"><text class="uni-calendar__box-bg-text">{{nowDate.month}}</text></view><view class="uni-calendar__weeks" style="padding-bottom: 7px;"><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{SUNText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{MONText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{TUEText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{WEDText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{THUText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{FRIText}}</text></view><view class="uni-calendar__weeks-day"><text class="uni-calendar__weeks-day-text">{{SATText}}</text></view></view><view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex"><view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex"><calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar":selected="selected" :checkHover="range" :disabledDate="disabledDate" @change="choiceDate"@handleMouse="handleMouse">
</calendar-item></view></view></view><view v-if="!insert && !range && hasTime" class="uni-date-changed uni-calendar--fixed-top"style="padding: 0 80px;"><view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view><time-picker type="time" :start="timepickerStartTime" :end="timepickerEndTime" v-model="time":disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style"></time-picker></view><view v-if="!insert && range && hasTime" class="uni-date-changed uni-calendar--fixed-top"><view class="uni-date-changed--time-start"><view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}</view><time-picker type="time" :start="timepickerStartTime" v-model="timeRange.startTime" :border="false":hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style"></time-picker></view><view style="line-height: 50px;"><uni-icons type="arrowthinright" color="#999"></uni-icons></view><view class="uni-date-changed--time-end"><view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view><time-picker type="time" :end="timepickerEndTime" v-model="timeRange.endTime" :border="false":hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style"></time-picker></view></view><view v-if="!insert" class="uni-date-changed uni-date-btn--ok"><view class="uni-datetime-picker--btn" @click="confirm">{{confirmText}}</view></view></view></view>
</template><script>import { Calendar, getDate, getTime } from './util.js';import calendarItem from './calendar-item.vue'import timePicker from './time-picker.vue'import { initVueI18n } from '@dcloudio/uni-i18n'import i18nMessages from './i18n/index.js'const { t } = initVueI18n(i18nMessages)/*** Calendar 日历* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等* @tutorial https://ext.dcloud.net.cn/plugin?id=56* @property {String} date 自定义当前时间,默认为今天* @property {String} startDate 日期选择范围-开始日期* @property {String} endDate 日期选择范围-结束日期* @property {Boolean} range 范围选择* @property {Boolean} insert = [true|false] 插入模式,默认为false* @value true 弹窗模式* @value false 插入模式* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]* @property {Boolean} showMonth 是否选择月份为背景* @property {[String} defaultValue 选择器打开时默认显示的时间* @event {Function} change 日期改变,`insert :ture` 时生效* @event {Function} confirm 确认选择`insert :false` 时生效* @event {Function} monthSwitch 切换月份时触发* @example <uni-calendar :insert="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />*/export default {components: {calendarItem,timePicker},props: {disabledDate:{type: Array,default: []},date: {type: String,default: ''},defTime: {type: [String, Object],default: ''},selectableTimes: {type: [Object],default () {return {}}},selected: {type: Array,default () {return []}},startDate: {type: String,default: ''},endDate: {type: String,default: ''},startPlaceholder: {type: String,default: ''},endPlaceholder: {type: String,default: ''},range: {type: Boolean,default: false},hasTime: {type: Boolean,default: false},insert: {type: Boolean,default: true},showMonth: {type: Boolean,default: true},clearDate: {type: Boolean,default: true},checkHover: {type: Boolean,default: true},hideSecond: {type: [Boolean],default: false},pleStatus: {type: Object,default () {return {before: '',after: '',data: [],fulldate: ''}}},defaultValue: {type: [String, Object, Array],default: ''}},data() {return {show: false,weeks: [],calendar: {},nowDate: {},aniMaskShow: false,firstEnter: true,time: '',timeRange: {startTime: '',endTime: ''},tempSingleDate: '',tempRange: {before: '',after: ''}}},watch: {date: {immediate: true,handler(newVal) {if (!this.range) {this.tempSingleDate = newValsetTimeout(() => {this.init(newVal)}, 100)}}},defTime: {immediate: true,handler(newVal) {if (!this.range) {this.time = newVal} else {this.timeRange.startTime = newVal.startthis.timeRange.endTime = newVal.end}}},startDate(val) {// 字节小程序 watch 早于 createdif(!this.cale){return}this.cale.setStartDate(val)this.cale.setDate(this.nowDate.fullDate)this.weeks = this.cale.weeks},endDate(val) {// 字节小程序 watch 早于 createdif(!this.cale){return}this.cale.setEndDate(val)this.cale.setDate(this.nowDate.fullDate)this.weeks = this.cale.weeks},selected(newVal) {// 字节小程序 watch 早于 createdif(!this.cale){return}this.cale.setSelectInfo(this.nowDate.fullDate, newVal)this.weeks = this.cale.weeks},pleStatus: {immediate: true,handler(newVal) {const {before,after,fulldate,which} = newValthis.tempRange.before = beforethis.tempRange.after = aftersetTimeout(() => {if (fulldate) {this.cale.setHoverMultiple(fulldate)if (before && after) {this.cale.lastHover = trueif (this.rangeWithinMonth(after, before)) returnthis.setDate(before)} else {this.cale.setMultiple(fulldate)this.setDate(this.nowDate.fullDate)this.calendar.fullDate = ''this.cale.lastHover = false}} else {// 字节小程序 watch 早于 createdif(!this.cale){return}this.cale.setDefaultMultiple(before, after)if (which === 'left' && before) {this.setDate(before)this.weeks = this.cale.weeks} else if(after) {this.setDate(after)this.weeks = this.cale.weeks}this.cale.lastHover = true}}, 16)}}},computed: {timepickerStartTime() {const activeDate = this.range ? this.tempRange.before : this.calendar.fullDatereturn activeDate === this.startDate ? this.selectableTimes.start : ''},timepickerEndTime() {const activeDate = this.range ? this.tempRange.after : this.calendar.fullDatereturn activeDate === this.endDate ? this.selectableTimes.end : ''},/*** for i18n*/selectDateText() {return t("uni-datetime-picker.selectDate")},startDateText() {return this.startPlaceholder || t("uni-datetime-picker.startDate")},endDateText() {return this.endPlaceholder || t("uni-datetime-picker.endDate")},okText() {return t("uni-datetime-picker.ok")},yearText() {return t("uni-datetime-picker.year")},monthText() {return t("uni-datetime-picker.month")},MONText() {return t("uni-calender.MON")},TUEText() {return t("uni-calender.TUE")},WEDText() {return t("uni-calender.WED")},THUText() {return t("uni-calender.THU")},FRIText() {return t("uni-calender.FRI")},SATText() {return t("uni-calender.SAT")},SUNText() {return t("uni-calender.SUN")},confirmText() {return t("uni-calender.confirm")},},created() {// 获取日历方法实例this.cale = new Calendar({selected: this.selected,startDate: this.startDate,endDate: this.endDate,range: this.range,})// 选中某一天this.init(this.date)},methods: {leaveCale() {this.firstEnter = true},handleMouse(weeks) {if (weeks.disable) returnif (this.cale.lastHover) returnlet {before,after} = this.cale.multipleStatusif (!before) returnthis.calendar = weeks// 设置范围选this.cale.setHoverMultiple(this.calendar.fullDate)this.weeks = this.cale.weeks// hover时,进入一个日历,更新另一个if (this.firstEnter) {this.$emit('firstEnterCale', this.cale.multipleStatus)this.firstEnter = false}},rangeWithinMonth(A, B) {const [yearA, monthA] = A.split('-')const [yearB, monthB] = B.split('-')return yearA === yearB && monthA === monthB},// 蒙版点击事件maskClick() {this.close()this.$emit('maskClose')},clearCalender() {if (this.range) {this.timeRange.startTime = ''this.timeRange.endTime = ''this.tempRange.before = ''this.tempRange.after = ''this.cale.multipleStatus.before = ''this.cale.multipleStatus.after = ''this.cale.multipleStatus.data = []this.cale.lastHover = false} else {this.time = ''this.tempSingleDate = ''}this.calendar.fullDate = ''this.setDate(new Date())},bindDateChange(e) {const value = e.detail.value + '-1'this.setDate(value)},/*** 初始化日期显示* @param {Object} date*/init(date) {// 字节小程序 watch 早于 createdif(!this.cale){return}this.cale.setDate(date || new Date())this.weeks = this.cale.weeksthis.nowDate = this.cale.getInfo(date)this.calendar = {...this.nowDate}if(!date){// 优化date为空默认不选中今天this.calendar.fullDate = ''if(this.defaultValue && !this.range){// 暂时只支持移动端非范围选择const defaultDate = new Date(this.defaultValue)const fullDate = getDate(defaultDate)const year = defaultDate.getFullYear()const month = defaultDate.getMonth()+1const date = defaultDate.getDate()const day = defaultDate.getDay()this.calendar = {fullDate,year,month,date,day},this.tempSingleDate = fullDatethis.time = getTime(defaultDate, this.hideSecond)}}},/*** 打开日历弹窗*/open() {// 弹窗模式并且清理数据if (this.clearDate && !this.insert) {this.cale.cleanMultipleStatus()this.init(this.date)}this.show = truethis.$nextTick(() => {setTimeout(() => {this.aniMaskShow = true}, 50)})},/*** 关闭日历弹窗*/close() {this.aniMaskShow = falsethis.$nextTick(() => {setTimeout(() => {this.show = falsethis.$emit('close')}, 300)})},/*** 确认按钮*/confirm() {this.setEmit('confirm')this.close()},/*** 变化触发*/change() {if (!this.insert) returnthis.setEmit('change')},/*** 选择月份触发*/monthSwitch() {let {year,month} = this.nowDatethis.$emit('monthSwitch', {year,month: Number(month)})},/*** 派发事件* @param {Object} name*/setEmit(name) {if(!this.range){if(!this.calendar.fullDate){this.calendar = this.cale.getInfo(new Date())this.tempSingleDate = this.calendar.fullDate}if(this.hasTime && !this.time) {this.time = getTime(new Date(), this.hideSecond)}}let {year,month,date,fullDate,extraInfo} = this.calendarthis.$emit(name, {range: this.cale.multipleStatus,year,month,date,time: this.time,timeRange: this.timeRange,fulldate: fullDate,extraInfo: extraInfo || {}})},/*** 选择天触发* @param {Object} weeks*/choiceDate(weeks) {if (weeks.disable) returnthis.calendar = weeksthis.calendar.userChecked = true// 设置多选this.cale.setMultiple(this.calendar.fullDate, true)this.weeks = this.cale.weeksthis.tempSingleDate = this.calendar.fullDateconst beforeDate = new Date(this.cale.multipleStatus.before).getTime()const afterDate = new Date(this.cale.multipleStatus.after).getTime()if (beforeDate > afterDate && afterDate) {this.tempRange.before = this.cale.multipleStatus.afterthis.tempRange.after = this.cale.multipleStatus.before} else {this.tempRange.before = this.cale.multipleStatus.beforethis.tempRange.after = this.cale.multipleStatus.after}this.change()},changeMonth(type) {let newDateif(type === 'pre') {newDate = this.cale.getPreMonthObj(this.nowDate.fullDate).fullDate} else if(type === 'next') {newDate = this.cale.getNextMonthObj(this.nowDate.fullDate).fullDate}this.setDate(newDate)this.monthSwitch()},/*** 设置日期* @param {Object} date*/setDate(date) {this.cale.setDate(date)this.weeks = this.cale.weeksthis.nowDate = this.cale.getInfo(date)}}}
</script><style lang="scss" >$uni-primary: #007aff !default;.uni-calendar {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: column;}.uni-calendar__mask {position: fixed;bottom: 0;top: 0;left: 0;right: 0;background-color: rgba(0, 0, 0, 0.4);transition-property: opacity;transition-duration: 0.3s;opacity: 0;/* #ifndef APP-NVUE */z-index: 99;/* #endif */}.uni-calendar--mask-show {opacity: 1}.uni-calendar--fixed {position: fixed;bottom: calc(var(--window-bottom));left: 0;right: 0;transition-property: transform;transition-duration: 0.3s;transform: translateY(460px);/* #ifndef APP-NVUE */z-index: 99;/* #endif */}.uni-calendar--ani-show {transform: translateY(0);}.uni-calendar__content {background-color: #fff;}.uni-calendar__content-mobile {border-top-left-radius: 10px;border-top-right-radius: 10px;box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);}.uni-calendar__header {position: relative;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;justify-content: center;align-items: center;height: 50px;}.uni-calendar__header-mobile {padding: 10px;padding-bottom: 0;}.uni-calendar--fixed-top {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;justify-content: space-between;border-top-color: rgba(0, 0, 0, 0.4);border-top-style: solid;border-top-width: 1px;}.uni-calendar--fixed-width {width: 50px;}.uni-calendar__backtoday {position: absolute;right: 0;top: 25rpx;padding: 0 5px;padding-left: 10px;height: 25px;line-height: 25px;font-size: 12px;border-top-left-radius: 25px;border-bottom-left-radius: 25px;color: #fff;background-color: #f1f1f1;}.uni-calendar__header-text {text-align: center;width: 100px;font-size: 15px;color: #666;}.uni-calendar__button-text {text-align: center;width: 100px;font-size: 14px;color: $uni-primary;/* #ifndef APP-NVUE */letter-spacing: 3px;/* #endif */}.uni-calendar__header-btn-box {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;align-items: center;justify-content: center;width: 50px;height: 50px;}.uni-calendar__header-btn {width: 9px;height: 9px;border-left-color: #808080;border-left-style: solid;border-left-width: 1px;border-top-color: #555555;border-top-style: solid;border-top-width: 1px;}.uni-calendar--left {transform: rotate(-45deg);}.uni-calendar--right {transform: rotate(135deg);}.uni-calendar__weeks {position: relative;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;}.uni-calendar__weeks-item {flex: 1;}.uni-calendar__weeks-day {flex: 1;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: column;justify-content: center;align-items: center;height: 40px;border-bottom-color: #F5F5F5;border-bottom-style: solid;border-bottom-width: 1px;}.uni-calendar__weeks-day-text {font-size: 12px;color: #B2B2B2;}.uni-calendar__box {position: relative;// padding: 0 10px;padding-bottom: 7px;}.uni-calendar__box-bg {/* #ifndef APP-NVUE */display: flex;/* #endif */justify-content: center;align-items: center;position: absolute;top: 0;left: 0;right: 0;bottom: 0;}.uni-calendar__box-bg-text {font-size: 200px;font-weight: bold;color: #999;opacity: 0.1;text-align: center;/* #ifndef APP-NVUE */line-height: 1;/* #endif */}.uni-date-changed {padding: 0 10px;// line-height: 50px;text-align: center;color: #333;border-top-color: #DCDCDC;;border-top-style: solid;border-top-width: 1px;flex: 1;}.uni-date-btn--ok {padding: 20px 15px;}.uni-date-changed--time-start {/* #ifndef APP-NVUE */display: flex;/* #endif */align-items: center;}.uni-date-changed--time-end {/* #ifndef APP-NVUE */display: flex;/* #endif */align-items: center;}.uni-date-changed--time-date {color: #999;line-height: 50px;/* #ifdef MP-TOUTIAO */font-size: 16px;/* #endif */margin-right: 5px;// opacity: 0.6;}.time-picker-style {// width: 62px;/* #ifndef APP-NVUE */display: flex;/* #endif */justify-content: center;align-items: center}.mr-10 {margin-right: 10px;}.dialog-close {position: absolute;top: 0;right: 0;bottom: 0;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;align-items: center;padding: 0 25px;margin-top: 10px;}.dialog-close-plus {width: 16px;height: 2px;background-color: #737987;border-radius: 2px;transform: rotate(45deg);}.dialog-close-rotate {position: absolute;transform: rotate(-45deg);}.uni-datetime-picker--btn {border-radius: 100px;height: 40px;line-height: 40px;background-color: $uni-primary;color: #fff;font-size: 16px;letter-spacing: 2px;}/* #ifndef APP-NVUE */.uni-datetime-picker--btn:active {opacity: 0.7;}/* #endif */
</style>
calendar-item.vue内
<template><view class="uni-calendar-item__weeks-box" :class="{'uni-calendar-item--disable':weeks.disable,'uni-calendar-item--before-checked-x':weeks.beforeMultiple,'uni-calendar-item--multiple': weeks.multiple,'uni-calendar-item--after-checked-x':weeks.afterMultiple,}" @click="choiceDate(weeks)" @mouseenter="handleMousemove(weeks)">
<view class="uni-calendar-item__weeks-box-item" :class="{'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover) && !disabledDate.includes(weeks.fullDate),'uni-calendar-item--checked-range-text': checkHover,'uni-calendar-item--before-checked':weeks.beforeMultiple,'uni-calendar-item--multiple': weeks.multiple,'uni-calendar-item--after-checked':weeks.afterMultiple,'uni-calendar-item--disable':weeks.disable,'uni-calendar-item--disable':disabledDate.includes(weeks.fullDate)}"><text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text><text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text></view><view :class="{'uni-calendar-item--today': weeks.isToday}"></view></view>
</template><script>export default {props: {disabledDate:{type: Array,default: []},weeks: {type: Object,default () {return {}}},calendar: {type: Object,default: () => {return {}}},selected: {type: Array,default: () => {return []}},checkHover: {type: Boolean,default: false}},methods: {choiceDate(weeks) {this.$emit('change', weeks)},handleMousemove(weeks) {this.$emit('handleMouse', weeks)}}}
</script><style lang="scss" >$uni-primary: #007aff !default;.uni-calendar-item__weeks-box {flex: 1;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: column;justify-content: center;align-items: center;margin: 1px 0;position: relative;}.uni-calendar-item__weeks-box-text {font-size: 14px;// font-family: Lato-Bold, Lato;font-weight: bold;color: darken($color: $uni-primary, $amount: 40%);}.uni-calendar-item__weeks-box-item {position: relative;/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: column;justify-content: center;align-items: center;width: 40px;height: 40px;/* #ifdef H5 */cursor: pointer;/* #endif */}.uni-calendar-item__weeks-box-circle {position: absolute;top: 5px;right: 5px;width: 8px;height: 8px;border-radius: 8px;background-color: #dd524d;}.uni-calendar-item__weeks-box .uni-calendar-item--disable {cursor: default;}.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable {color: #D1D1D1;}.uni-calendar-item--today {position: absolute;top: 10px;right: 17%;background-color: #dd524d;width:6px;height: 6px;border-radius: 50%;}.uni-calendar-item--extra {color: #dd524d;opacity: 0.8;}.uni-calendar-item__weeks-box .uni-calendar-item--checked {background-color: $uni-primary;border-radius: 50%;box-sizing: border-box;border: 3px solid #fff;}.uni-calendar-item--checked .uni-calendar-item--checked-text {color: #fff;}.uni-calendar-item--multiple .uni-calendar-item--checked-range-text {color: #333;}.uni-calendar-item--multiple {background-color: #F6F7FC;// color: #fff;}.uni-calendar-item--multiple .uni-calendar-item--before-checked,.uni-calendar-item--multiple .uni-calendar-item--after-checked {background-color: $uni-primary;border-radius: 50%;box-sizing: border-box;border: 3px solid #F6F7FC;}.uni-calendar-item--before-checked .uni-calendar-item--checked-text,.uni-calendar-item--after-checked .uni-calendar-item--checked-text {color: #fff;}.uni-calendar-item--before-checked-x {border-top-left-radius: 50px;border-bottom-left-radius: 50px;box-sizing: border-box;background-color: #F6F7FC;}.uni-calendar-item--after-checked-x {border-top-right-radius: 50px;border-bottom-right-radius: 50px;background-color: #F6F7FC;}
</style>
uni-datetime-picker.vue内
<template><view class="uni-date"><view class="uni-date-editor" @click="show"><slot><viewclass="uni-date-editor--x":class="{'uni-date-editor--x__disabled': disabled,'uni-date-x--border': border}"><view v-if="!isRange" class="uni-date-x uni-date-single"><uni-icons class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons><view class="uni-date__x-input">{{ displayValue || singlePlaceholderText }}</view></view><view v-else class="uni-date-x uni-date-range"><uni-icons class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons><view class="uni-date__x-input text-center">{{ displayRangeValue.startDate || startPlaceholderText }}</view><view class="range-separator">{{rangeSeparator}}</view><view class="uni-date__x-input text-center">{{ displayRangeValue.endDate || endPlaceholderText }}</view></view><view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear"><uni-icons type="clear" color="#c0c4cc" size="22"></uni-icons></view></view></slot></view><view v-show="pickerVisible" class="uni-date-mask--pc" @click="close"></view><view v-if="!isPhone" v-show="pickerVisible" ref="datePicker" class="uni-date-picker__container"><view v-if="!isRange" class="uni-date-single--x" :style="pickerPositionStyle"><view class="uni-popper__arrow"></view><view v-if="hasTime" class="uni-date-changed popup-x-header"><input class="uni-date__input text-center" type="text" v-model="inputDate":placeholder="selectDateText" /><time-picker type="time" v-model="pickerTime" :border="false" :disabled="!inputDate":start="timepickerStartTime" :end="timepickerEndTime" :hideSecond="hideSecond" style="width: 100%;"><input class="uni-date__input text-center" type="text" v-model="pickerTime" :placeholder="selectTimeText":disabled="!inputDate" /></time-picker></view><Calendar ref="pcSingle" :showMonth="false" :start-date="calendarRange.startDate":end-date="calendarRange.endDate" :date="calendarDate" @change="singleChange":default-value="defaultValue"style="padding: 0 8px;" /><view v-if="hasTime" class="popup-x-footer"><text class="confirm-text" @click="confirmSingleChange">{{okText}}</text></view></view><view v-else class="uni-date-range--x" :style="pickerPositionStyle"><view class="uni-popper__arrow"></view><view v-if="hasTime" class="popup-x-header uni-date-changed"><view class="popup-x-header--datetime"><input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.startDate":placeholder="startDateText" /><time-picker type="time" v-model="tempRange.startTime" :start="timepickerStartTime" :border="false":disabled="!tempRange.startDate" :hideSecond="hideSecond"><input class="uni-date__input uni-date-range__input" type="text"v-model="tempRange.startTime" :placeholder="startTimeText":disabled="!tempRange.startDate" /></time-picker></view><uni-icons type="arrowthinright" color="#999" style="line-height: 40px;"></uni-icons><view class="popup-x-header--datetime"><input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endDate":placeholder="endDateText" /><time-picker type="time" v-model="tempRange.endTime" :end="timepickerEndTime" :border="false":disabled="!tempRange.endDate" :hideSecond="hideSecond"><input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endTime":placeholder="endTimeText" :disabled="!tempRange.endDate" /></time-picker></view></view><view class="popup-x-body"><calendar ref="left" :showMonth="false" :start-date="caleRange.startDate":end-date="caleRange.endDate" :range="true" :disabledDate="disabledDate" @change="leftChange" :pleStatus="endMultipleStatus"@firstEnterCale="updateRightCale" @monthSwitch="leftMonthSwitch" style="padding: 0 8px;" /><calendar ref="right" :showMonth="false" :start-date="caleRange.startDate":end-date="caleRange.endDate" :range="true" @change="rightChange":pleStatus="startMultipleStatus" :disabledDate="disabledDate" @firstEnterCale="updateLeftCale"@monthSwitch="rightMonthSwitch" style="padding: 0 8px;border-left: 1px solid #F1F1F1;" /></view><view v-if="hasTime" class="popup-x-footer"><text @click="clear">{{clearText}}</text><text class="confirm-text" @click="confirmRangeChange">{{okText}}</text></view></view></view><Calendar v-if="isPhone" ref="mobile" :clearDate="false" :date="calendarDate" :defTime="mobileCalendarTime":start-date="calendarRange.startDate" :end-date="calendarRange.endDate" :selectableTimes="mobSelectableTime":startPlaceholder="startPlaceholder" :endPlaceholder="endPlaceholder":default-value="defaultValue":pleStatus="endMultipleStatus" :showMonth="false" :disabledDate="disabledDate" :range="isRange" :hasTime="hasTime" :insert="false":hideSecond="hideSecond" @confirm="mobileChange" @maskClose="close" /></view>
</template>
<script>/*** DatetimePicker 时间选择器* @description 同时支持 PC 和移动端使用日历选择日期和日期范围* @tutorial https://ext.dcloud.net.cn/plugin?id=3962* @property {String} type 选择器类型* @property {String|Number|Array|Date} value 绑定值* @property {String} placeholder 单选择时的占位内容* @property {String} start 起始时间* @property {String} end 终止时间* @property {String} start-placeholder 范围选择时开始日期的占位内容* @property {String} end-placeholder 范围选择时结束日期的占位内容* @property {String} range-separator 选择范围时的分隔符* @property {Boolean} border = [true|false] 是否有边框* @property {Boolean} disabled = [true|false] 是否禁用* @property {Boolean} clearIcon = [true|false] 是否显示清除按钮(仅PC端适用)* @property {[String} defaultValue 选择器打开时默认显示的时间* @event {Function} change 确定日期时触发的事件* @event {Function} maskClick 点击遮罩层触发的事件* @event {Function} show 打开弹出层* @event {Function} close 关闭弹出层* @event {Function} clear 清除上次选中的状态和值**/import Calendar from './calendar.vue'import TimePicker from './time-picker.vue'import { initVueI18n } from '@dcloudio/uni-i18n'import i18nMessages from './i18n/index.js'import { getDateTime, getDate, getTime, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat } from './util'export default {name: 'UniDatetimePicker',options: {virtualHost: true},components: {Calendar,TimePicker},data() {return {isRange: false,hasTime: false,displayValue: '',inputDate: '',calendarDate: '',pickerTime: '',calendarRange: {startDate: '',startTime: '',endDate: '',endTime: ''},displayRangeValue: {startDate: '',endDate: '',},tempRange: {startDate: '',startTime: '',endDate: '',endTime: ''},// 左右日历同步数据startMultipleStatus: {before: '',after: '',data: [],fulldate: ''},endMultipleStatus: {before: '',after: '',data: [],fulldate: ''},pickerVisible: false,pickerPositionStyle: null,isEmitValue: false,isPhone: false,isFirstShow: true,i18nT: () => {}}},props: {disabledDate:{type: Array,default: []},type: {type: String,default: 'datetime'},value: {type: [String, Number, Array, Date],default: ''},modelValue: {type: [String, Number, Array, Date],default: ''},start: {type: [Number, String],default: ''},end: {type: [Number, String],default: ''},returnType: {type: String,default: 'string'},placeholder: {type: String,default: ''},startPlaceholder: {type: String,default: ''},endPlaceholder: {type: String,default: ''},rangeSeparator: {type: String,default: '-'},border: {type: [Boolean],default: true},disabled: {type: [Boolean],default: false},clearIcon: {type: [Boolean],default: true},hideSecond: {type: [Boolean],default: false},defaultValue: {type: [String, Object, Array],default: ''}},watch: {type: {immediate: true,handler(newVal) {this.hasTime = newVal.indexOf('time') !== -1this.isRange = newVal.indexOf('range') !== -1}},// #ifndef VUE3value: {immediate: true,handler(newVal) {if (this.isEmitValue) {this.isEmitValue = falsereturn}this.initPicker(newVal)}},// #endif// #ifdef VUE3modelValue: {immediate: true,handler(newVal) {if (this.isEmitValue) {this.isEmitValue = falsereturn}this.initPicker(newVal)}},// #endifstart: {immediate: true,handler(newVal) {if (!newVal) returnthis.calendarRange.startDate = getDate(newVal)if (this.hasTime) {this.calendarRange.startTime = getTime(newVal)}}},end: {immediate: true,handler(newVal) {if (!newVal) returnthis.calendarRange.endDate = getDate(newVal)if (this.hasTime) {this.calendarRange.endTime = getTime(newVal, this.hideSecond)}}},},computed: {timepickerStartTime() {const activeDate = this.isRange ? this.tempRange.startDate : this.inputDatereturn activeDate === this.calendarRange.startDate ? this.calendarRange.startTime : ''},timepickerEndTime() {const activeDate = this.isRange ? this.tempRange.endDate : this.inputDatereturn activeDate === this.calendarRange.endDate ? this.calendarRange.endTime : ''},mobileCalendarTime() {const timeRange = {start: this.tempRange.startTime,end: this.tempRange.endTime}return this.isRange ? timeRange : this.pickerTime},mobSelectableTime() {return {start: this.calendarRange.startTime,end: this.calendarRange.endTime}},datePopupWidth() {// todoreturn this.isRange ? 653 : 301},/*** for i18n*/singlePlaceholderText() {return this.placeholder || (this.type === 'date' ? this.selectDateText : this.selectDateTimeText)},startPlaceholderText() {return this.startPlaceholder || this.startDateText},endPlaceholderText() {return this.endPlaceholder || this.endDateText},selectDateText() {return this.i18nT("uni-datetime-picker.selectDate")},selectDateTimeText() {return this.i18nT("uni-datetime-picker.selectDateTime")},selectTimeText() {return this.i18nT("uni-datetime-picker.selectTime")},startDateText() {return this.startPlaceholder || this.i18nT("uni-datetime-picker.startDate")},startTimeText() {return this.i18nT("uni-datetime-picker.startTime")},endDateText() {return this.endPlaceholder || this.i18nT("uni-datetime-picker.endDate")},endTimeText() {return this.i18nT("uni-datetime-picker.endTime")},okText() {return this.i18nT("uni-datetime-picker.ok")},clearText() {return this.i18nT("uni-datetime-picker.clear")},showClearIcon() {return this.clearIcon && !this.disabled && (this.displayValue || (this.displayRangeValue.startDate && this.displayRangeValue.endDate))}},created() {this.initI18nT()this.platform()},methods: {initI18nT() {const vueI18n = initVueI18n(i18nMessages)this.i18nT = vueI18n.t},initPicker(newVal) {if ((!newVal && !this.defaultValue) || Array.isArray(newVal) && !newVal.length) {this.$nextTick(() => {this.clear(false)})return}if (!Array.isArray(newVal) && !this.isRange) {if(newVal){this.displayValue = this.inputDate = this.calendarDate = getDate(newVal)if (this.hasTime) {this.pickerTime = getTime(newVal, this.hideSecond)this.displayValue = `${this.displayValue} ${this.pickerTime}`}}else if(this.defaultValue){this.inputDate = this.calendarDate = getDate(this.defaultValue)if(this.hasTime){this.pickerTime = getTime(this.defaultValue, this.hideSecond)}}} else {const [before, after] = newValif (!before && !after) returnconst beforeDate = getDate(before)const beforeTime = getTime(before, this.hideSecond)const afterDate = getDate(after)const afterTime = getTime(after, this.hideSecond)const startDate = beforeDateconst endDate = afterDatethis.displayRangeValue.startDate = this.tempRange.startDate = startDatethis.displayRangeValue.endDate = this.tempRange.endDate = endDateif (this.hasTime) {this.displayRangeValue.startDate = `${beforeDate} ${beforeTime}`this.displayRangeValue.endDate = `${afterDate} ${afterTime}`this.tempRange.startTime = beforeTimethis.tempRange.endTime = afterTime}const defaultRange = {before: beforeDate,after: afterDate}this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, {which: 'right'})this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, defaultRange, {which: 'left'})}},updateLeftCale(e) {const left = this.$refs.left// 设置范围选left.cale.setHoverMultiple(e.after)left.setDate(this.$refs.left.nowDate.fullDate)},updateRightCale(e) {const right = this.$refs.right// 设置范围选right.cale.setHoverMultiple(e.after)right.setDate(this.$refs.right.nowDate.fullDate)},platform() {if(typeof navigator !== "undefined"){this.isPhone = navigator.userAgent.toLowerCase().indexOf('mobile') !== -1return}const { windowWidth } = uni.getSystemInfoSync()this.isPhone = windowWidth <= 500this.windowWidth = windowWidth},show() {if (this.disabled) {return}this.platform()if (this.isPhone) {setTimeout(() => {this.$refs.mobile.open()}, 0);return}this.pickerPositionStyle = {top: '10px'}const dateEditor = uni.createSelectorQuery().in(this).select(".uni-date-editor")dateEditor.boundingClientRect(rect => {if (this.windowWidth - rect.left < this.datePopupWidth) {this.pickerPositionStyle.right = 0}}).exec()setTimeout(() => {this.pickerVisible = !this.pickerVisibleif (!this.isPhone && this.isRange && this.isFirstShow) {this.isFirstShow = falseconst {startDate,endDate} = this.calendarRangeif (startDate && endDate) {if (this.diffDate(startDate, endDate) < 30) {this.$refs.right.changeMonth('pre')}} else {this.$refs.right.changeMonth('next')if(this.isPhone){this.$refs.right.cale.lastHover = false;}}}}, 50)},close() {setTimeout(() => {this.pickerVisible = falsethis.$emit('maskClick', this.value)this.$refs.mobile && this.$refs.mobile.close()}, 20)},setEmit(value) {if (this.returnType === "timestamp" || this.returnType === "date") {if (!Array.isArray(value)) {if (!this.hasTime) {value = value + ' ' + '00:00:00'}value = this.createTimestamp(value)if (this.returnType === "date") {value = new Date(value)}} else {if (!this.hasTime) {value[0] = value[0] + ' ' + '00:00:00'value[1] = value[1] + ' ' + '00:00:00'}value[0] = this.createTimestamp(value[0])value[1] = this.createTimestamp(value[1])if (this.returnType === "date") {value[0] = new Date(value[0])value[1] = new Date(value[1])}}}this.$emit('update:modelValue', value)this.$emit('input', value)this.$emit('change', value)this.isEmitValue = true},createTimestamp(date) {date = fixIosDateFormat(date)return Date.parse(new Date(date))},singleChange(e) {this.calendarDate = this.inputDate = e.fulldateif (this.hasTime) returnthis.confirmSingleChange()},confirmSingleChange() {if(!checkDate(this.inputDate)){const now = new Date()this.calendarDate = this.inputDate = getDate(now)this.pickerTime = getTime(now, this.hideSecond)}let startLaterInputDate = falselet startDate, startTimeif(this.start) {let startString = this.startif(typeof this.start === 'number'){startString = getDateTime(this.start, this.hideSecond)}[startDate, startTime] = startString.split(' ')if(this.start && !dateCompare(startDate, this.inputDate)) {startLaterInputDate = truethis.inputDate = startDate}}let endEarlierInputDate = falselet endDate, endTimeif(this.end) {let endString = this.endif(typeof this.end === 'number'){endString = getDateTime(this.end, this.hideSecond)}[endDate, endTime] = endString.split(' ')if(this.end && !dateCompare(this.inputDate, endDate)) {endEarlierInputDate = truethis.inputDate = endDate}}if (this.hasTime) {if(startLaterInputDate){this.pickerTime = startTime || getDefaultSecond(this.hideSecond)}if(endEarlierInputDate){this.pickerTime = endTime || getDefaultSecond(this.hideSecond)}if(!this.pickerTime){this.pickerTime = getTime(Date.now(), this.hideSecond)}this.displayValue = `${this.inputDate} ${this.pickerTime}`} else {this.displayValue = this.inputDate}this.setEmit(this.displayValue)this.pickerVisible = false},leftChange(e) {const {before,after} = e.rangethis.rangeChange(before, after)const obj = {before: e.range.before,after: e.range.after,data: e.range.data,fulldate: e.fulldate}this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, obj)},rightChange(e) {const {before,after} = e.rangethis.rangeChange(before, after)const obj = {before: e.range.before,after: e.range.after,data: e.range.data,fulldate: e.fulldate}this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, obj)},mobileChange(e) {if (this.isRange) {const {before, after} = e.rangeif(!before || !after){return}this.handleStartAndEnd(before, after, true)if (this.hasTime) {const {startTime,endTime} = e.timeRangethis.tempRange.startTime = startTimethis.tempRange.endTime = endTime}this.confirmRangeChange()} else {if (this.hasTime) {this.displayValue = e.fulldate + ' ' + e.time} else {this.displayValue = e.fulldate}this.setEmit(this.displayValue)}this.$refs.mobile.close()},rangeChange(before, after) {if (!(before && after)) returnthis.handleStartAndEnd(before, after, true)if (this.hasTime) returnthis.confirmRangeChange()},confirmRangeChange() {if (!this.tempRange.startDate || !this.tempRange.endDate) {this.pickerVisible = falsereturn}if(!checkDate(this.tempRange.startDate)){this.tempRange.startDate = getDate(Date.now())}if(!checkDate(this.tempRange.endDate)){this.tempRange.endDate = getDate(Date.now())}let start, endlet startDateLaterRangeStartDate = falselet startDateLaterRangeEndDate = falselet startDate, startTimeif(this.start) {let startString = this.startif(typeof this.start === 'number'){startString = getDateTime(this.start, this.hideSecond)}[startDate,startTime] = startString.split(' ')if(this.start && !dateCompare(this.start, this.tempRange.startDate)) {startDateLaterRangeStartDate = truethis.tempRange.startDate = startDate}if(this.start && !dateCompare(this.start, this.tempRange.endDate)) {startDateLaterRangeEndDate = truethis.tempRange.endDate = startDate}}let endDateEarlierRangeStartDate = falselet endDateEarlierRangeEndDate = falselet endDate, endTimeif(this.end) {let endString = this.endif(typeof this.end === 'number'){endString = getDateTime(this.end, this.hideSecond)}[endDate,endTime] = endString.split(' ')if(this.end && !dateCompare(this.tempRange.startDate, this.end)) {endDateEarlierRangeStartDate = truethis.tempRange.startDate = endDate}if(this.end && !dateCompare(this.tempRange.endDate, this.end)) {endDateEarlierRangeEndDate = truethis.tempRange.endDate = endDate}}if (!this.hasTime) {start = this.displayRangeValue.startDate = this.tempRange.startDateend = this.displayRangeValue.endDate = this.tempRange.endDate} else {if(startDateLaterRangeStartDate){this.tempRange.startTime = startTime || getDefaultSecond(this.hideSecond)}else if(endDateEarlierRangeStartDate){this.tempRange.startTime = endTime || getDefaultSecond(this.hideSecond)}if(!this.tempRange.startTime){this.tempRange.startTime = getTime(Date.now(), this.hideSecond)}if(startDateLaterRangeEndDate){this.tempRange.endTime = startTime || getDefaultSecond(this.hideSecond)}else if(endDateEarlierRangeEndDate){this.tempRange.endTime = endTime || getDefaultSecond(this.hideSecond)}if(!this.tempRange.endTime){this.tempRange.endTime = getTime(Date.now(), this.hideSecond)}start = this.displayRangeValue.startDate = `${this.tempRange.startDate} ${this.tempRange.startTime}`end = this.displayRangeValue.endDate = `${this.tempRange.endDate} ${this.tempRange.endTime}`}if(!dateCompare(start,end)){[start, end] = [end, start]}this.displayRangeValue.startDate = startthis.displayRangeValue.endDate = endconst displayRange = [start, end]this.setEmit(displayRange)this.pickerVisible = false},handleStartAndEnd(before, after, temp = false) {if (!(before && after)) returnconst type = temp ? 'tempRange' : 'range'const isStartEarlierEnd = dateCompare(before, after)this[type].startDate = isStartEarlierEnd ? before : afterthis[type].endDate = isStartEarlierEnd ? after : before},/*** 比较时间大小*/dateCompare(startDate, endDate) {// 计算截止时间startDate = new Date(startDate.replace('-', '/').replace('-', '/'))// 计算详细项的截止时间endDate = new Date(endDate.replace('-', '/').replace('-', '/'))return startDate <= endDate},/*** 比较时间差*/diffDate(startDate, endDate) {// 计算截止时间startDate = new Date(startDate.replace('-', '/').replace('-', '/'))// 计算详细项的截止时间endDate = new Date(endDate.replace('-', '/').replace('-', '/'))const diff = (endDate - startDate) / (24 * 60 * 60 * 1000)return Math.abs(diff)},clear(needEmit = true) {if (!this.isRange) {this.displayValue = ''this.inputDate = ''this.pickerTime = ''if (this.isPhone) {this.$refs.mobile && this.$refs.mobile.clearCalender()} else {this.$refs.pcSingle && this.$refs.pcSingle.clearCalender()}if (needEmit) {this.$emit('change', '')this.$emit('input', '')this.$emit('update:modelValue', '')}} else {this.displayRangeValue.startDate = ''this.displayRangeValue.endDate = ''this.tempRange.startDate = ''this.tempRange.startTime = ''this.tempRange.endDate = ''this.tempRange.endTime = ''if (this.isPhone) {this.$refs.mobile && this.$refs.mobile.clearCalender()} else {this.$refs.left && this.$refs.left.clearCalender()this.$refs.right && this.$refs.right.clearCalender()this.$refs.right && this.$refs.right.changeMonth('next')}if (needEmit) {this.$emit('change', [])this.$emit('input', [])this.$emit('update:modelValue', [])}}}}}
</script><style lang="scss">$uni-primary: #007aff !default;.uni-date {width: 100%;flex: 1;}.uni-date-x {display: flex;flex-direction: row;align-items: center;justify-content: center;border-radius: 4px;background-color: #fff;color: #666;font-size: 14px;flex: 1;.icon-calendar{padding-left: 3px;}.range-separator{height: 35px;/* #ifndef MP */padding: 0 2px;/* #endif */line-height: 35px;}}.uni-date-x--border {box-sizing: border-box;border-radius: 4px;border: 1px solid #e5e5e5;}.uni-date-editor--x {display: flex;align-items: center;position: relative;}.uni-date-editor--x .uni-date__icon-clear {padding-right: 3px;display: flex;align-items: center;/* #ifdef H5 */cursor: pointer;/* #endif */}.uni-date__x-input {width: auto;height: 35px;/* #ifndef MP */padding-left: 5px;/* #endif */position: relative;flex: 1;line-height: 35px;font-size: 14px;overflow: hidden;}.text-center {text-align: center;}.uni-date__input {height: 40px;width: 100%;line-height: 40px;font-size: 14px;}.uni-date-range__input {text-align: center;max-width: 142px;}.uni-date-picker__container {position: relative;}.uni-date-mask--pc {position: fixed;bottom: 0px;top: 0px;left: 0px;right: 0px;background-color: rgba(0, 0, 0, 0);transition-duration: 0.3s;z-index: 996;}.uni-date-single--x {background-color: #fff;position: absolute;top: 0;z-index: 999;border: 1px solid #EBEEF5;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);border-radius: 4px;}.uni-date-range--x {background-color: #fff;position: absolute;top: 0;z-index: 999;border: 1px solid #EBEEF5;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);border-radius: 4px;}.uni-date-editor--x__disabled {opacity: 0.4;cursor: default;}.uni-date-editor--logo {width: 16px;height: 16px;vertical-align: middle;}/* 添加时间 */.popup-x-header {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;}.popup-x-header--datetime {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;flex: 1;}.popup-x-body {display: flex;}.popup-x-footer {padding: 0 15px;border-top-color: #F1F1F1;border-top-style: solid;border-top-width: 1px;line-height: 40px;text-align: right;color: #666;}.popup-x-footer text:hover {color: $uni-primary;cursor: pointer;opacity: 0.8;}.popup-x-footer .confirm-text {margin-left: 20px;color: $uni-primary;}.uni-date-changed {text-align: center;color: #333;border-bottom-color: #F1F1F1;border-bottom-style: solid;border-bottom-width: 1px;}.uni-date-changed--time text {height: 50px;line-height: 50px;}.uni-date-changed .uni-date-changed--time {flex: 1;}.uni-date-changed--time-date {color: #333;opacity: 0.6;}.mr-50 {margin-right: 50px;}/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */.uni-popper__arrow,.uni-popper__arrow::after {position: absolute;display: block;width: 0;height: 0;border: 6px solid transparent;border-top-width: 0;}.uni-popper__arrow {filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));top: -6px;left: 10%;margin-right: 3px;border-bottom-color: #EBEEF5;}.uni-popper__arrow::after {content: " ";top: 1px;margin-left: -6px;border-bottom-color: #fff;}
</style>
完成上述修改后
组件内引用
<uni-datetime-picker type="date" :disabledDate="disabledDate" />