| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927 | <template>	<view class="uni-datetime-picker">		<view @click="initTimePicker">			<slot>				<view class="uni-datetime-picker-timebox-pointer"					:class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}">					<text class="uni-datetime-picker-text">{{time}}</text>					<view v-if="!time" class="uni-datetime-picker-time">						<text class="uni-datetime-picker-text">{{selectTimeText}}</text>					</view>				</view>			</slot>		</view>		<view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>		<view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"			:style="fixNvueBug">			<view class="uni-title">				<text class="uni-datetime-picker-text">{{selectTimeText}}</text>			</view>			<view v-if="dateShow" class="uni-datetime-picker__container-box">				<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd"					@change="bindDateChange">					<picker-view-column>						<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>					<picker-view-column>						<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>					<picker-view-column>						<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>				</picker-view>				<!-- 兼容 nvue 不支持伪类 -->				<text class="uni-datetime-picker-sign sign-left">-</text>				<text class="uni-datetime-picker-sign sign-right">-</text>			</view>			<view v-if="timeShow" class="uni-datetime-picker__container-box">				<picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']"					:indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">					<picker-view-column>						<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>					<picker-view-column>						<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>					<picker-view-column v-if="!hideSecond">						<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">							<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>						</view>					</picker-view-column>				</picker-view>				<!-- 兼容 nvue 不支持伪类 -->				<text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>				<text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>			</view>			<view class="uni-datetime-picker-btn">				<view @click="clearTime">					<text class="uni-datetime-picker-btn-text">{{clearText}}</text>				</view>				<view class="uni-datetime-picker-btn-group">					<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">						<text class="uni-datetime-picker-btn-text">{{cancelText}}</text>					</view>					<view @click="setTime">						<text class="uni-datetime-picker-btn-text">{{okText}}</text>					</view>				</view>			</view>		</view>		<!-- #ifdef H5 -->		<!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> -->		<!-- #endif -->	</view></template><script>	// #ifdef H5	import keypress from './keypress'	// #endif	import {		initVueI18n	} from '@dcloudio/uni-i18n'	import messages from './i18n/index.js'	const {	t	} = initVueI18n(messages)	/**	 * DatetimePicker 时间选择器	 * @description 可以同时选择日期和时间的选择器	 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx	 * @property {String} type = [datetime | date | time] 显示模式	 * @property {Boolean} multiple = [true|false] 是否多选	 * @property {String|Number} value 默认值	 * @property {String|Number} start 起始日期或时间	 * @property {String|Number} end 起始日期或时间	 * @property {String} return-type = [timestamp | string]	 * @event {Function} change  选中发生变化触发	 */	export default {		name: 'UniDatetimePicker',		components: {			// #ifdef H5			keypress			// #endif		},		data() {			return {				indicatorStyle: `height: 50px;`,				visible: false,				fixNvueBug: {},				dateShow: true,				timeShow: true,				title: '日期和时间',				// 输入框当前时间				time: '',				// 当前的年月日时分秒				year: 1920,				month: 0,				day: 0,				hour: 0,				minute: 0,				second: 0,				// 起始时间				startYear: 1920,				startMonth: 1,				startDay: 1,				startHour: 0,				startMinute: 0,				startSecond: 0,				// 结束时间				endYear: 2120,				endMonth: 12,				endDay: 31,				endHour: 23,				endMinute: 59,				endSecond: 59,			}		},		props: {			type: {				type: String,				default: 'datetime'			},			value: {				type: [String, Number],				default: ''			},			modelValue: {				type: [String, Number],				default: ''			},			start: {				type: [Number, String],				default: ''			},			end: {				type: [Number, String],				default: ''			},			returnType: {				type: String,				default: 'string'			},			disabled: {				type: [Boolean, String],				default: false			},			border: {				type: [Boolean, String],				default: true			},			hideSecond: {				type: [Boolean, String],				default: false			}		},		watch: {			value: {				handler(newVal, oldVal) {					if (newVal) {						this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式						this.initTime(false)					} else {						this.time = ''						this.parseValue(Date.now())					}				},				immediate: true			},			type: {				handler(newValue) {					if (newValue === 'date') {						this.dateShow = true						this.timeShow = false						this.title = '日期'					} else if (newValue === 'time') {						this.dateShow = false						this.timeShow = true						this.title = '时间'					} else {						this.dateShow = true						this.timeShow = true						this.title = '日期和时间'					}				},				immediate: true			},			start: {				handler(newVal) {					this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式				},				immediate: true			},			end: {				handler(newVal) {					this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式				},				immediate: true			},			// 月、日、时、分、秒可选范围变化后,检查当前值是否在范围内,不在则当前值重置为可选范围第一项			months(newVal) {				this.checkValue('month', this.month, newVal)			},			days(newVal) {				this.checkValue('day', this.day, newVal)			},			hours(newVal) {				this.checkValue('hour', this.hour, newVal)			},			minutes(newVal) {				this.checkValue('minute', this.minute, newVal)			},			seconds(newVal) {				this.checkValue('second', this.second, newVal)			}		},		computed: {			// 当前年、月、日、时、分、秒选择范围			years() {				return this.getCurrentRange('year')			},			months() {				return this.getCurrentRange('month')			},			days() {				return this.getCurrentRange('day')			},			hours() {				return this.getCurrentRange('hour')			},			minutes() {				return this.getCurrentRange('minute')			},			seconds() {				return this.getCurrentRange('second')			},			// picker 当前值数组			ymd() {				return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]			},			hms() {				return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]			},			// 当前 date 是 start			currentDateIsStart() {				return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay			},			// 当前 date 是 end			currentDateIsEnd() {				return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay			},			// 当前年、月、日、时、分、秒的最小值和最大值			minYear() {				return this.startYear			},			maxYear() {				return this.endYear			},			minMonth() {				if (this.year === this.startYear) {					return this.startMonth				} else {					return 1				}			},			maxMonth() {				if (this.year === this.endYear) {					return this.endMonth				} else {					return 12				}			},			minDay() {				if (this.year === this.startYear && this.month === this.startMonth) {					return this.startDay				} else {					return 1				}			},			maxDay() {				if (this.year === this.endYear && this.month === this.endMonth) {					return this.endDay				} else {					return this.daysInMonth(this.year, this.month)				}			},			minHour() {				if (this.type === 'datetime') {					if (this.currentDateIsStart) {						return this.startHour					} else {						return 0					}				}				if (this.type === 'time') {					return this.startHour				}			},			maxHour() {				if (this.type === 'datetime') {					if (this.currentDateIsEnd) {						return this.endHour					} else {						return 23					}				}				if (this.type === 'time') {					return this.endHour				}			},			minMinute() {				if (this.type === 'datetime') {					if (this.currentDateIsStart && this.hour === this.startHour) {						return this.startMinute					} else {						return 0					}				}				if (this.type === 'time') {					if (this.hour === this.startHour) {						return this.startMinute					} else {						return 0					}				}			},			maxMinute() {				if (this.type === 'datetime') {					if (this.currentDateIsEnd && this.hour === this.endHour) {						return this.endMinute					} else {						return 59					}				}				if (this.type === 'time') {					if (this.hour === this.endHour) {						return this.endMinute					} else {						return 59					}				}			},			minSecond() {				if (this.type === 'datetime') {					if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {						return this.startSecond					} else {						return 0					}				}				if (this.type === 'time') {					if (this.hour === this.startHour && this.minute === this.startMinute) {						return this.startSecond					} else {						return 0					}				}			},			maxSecond() {				if (this.type === 'datetime') {					if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {						return this.endSecond					} else {						return 59					}				}				if (this.type === 'time') {					if (this.hour === this.endHour && this.minute === this.endMinute) {						return this.endSecond					} else {						return 59					}				}			},			/**			 * for i18n			 */			selectTimeText() {				return t("uni-datetime-picker.selectTime")			},			okText() {				return t("uni-datetime-picker.ok")			},			clearText() {				return t("uni-datetime-picker.clear")			},			cancelText() {				return t("uni-datetime-picker.cancel")			}		},		mounted() {			// #ifdef APP-NVUE			const res = uni.getSystemInfoSync();			this.fixNvueBug = {				top: res.windowHeight / 2,				left: res.windowWidth / 2			}			// #endif		},		methods: {			/**			 * @param {Object} item			 * 小于 10 在前面加个 0			 */			lessThanTen(item) {				return item < 10 ? '0' + item : item			},			/**			 * 解析时分秒字符串,例如:00:00:00			 * @param {String} timeString			 */			parseTimeType(timeString) {				if (timeString) {					let timeArr = timeString.split(':')					this.hour = Number(timeArr[0])					this.minute = Number(timeArr[1])					this.second = Number(timeArr[2])				}			},			/**			 * 解析选择器初始值,类型可以是字符串、时间戳,例如:2000-10-02、'08:30:00'、 1610695109000			 * @param {String | Number} datetime			 */			initPickerValue(datetime) {				let defaultValue = null				if (datetime) {					defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)				} else {					defaultValue = Date.now()					defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)				}				this.parseValue(defaultValue)			},			/**			 * 初始值规则:			 * - 用户设置初始值 value			 * 	- 设置了起始时间 start、终止时间 end,并 start < value < end,初始值为 value, 否则初始值为 start			 * 	- 只设置了起始时间 start,并 start < value,初始值为 value,否则初始值为 start			 * 	- 只设置了终止时间 end,并 value < end,初始值为 value,否则初始值为 end			 * 	- 无起始终止时间,则初始值为 value			 * - 无初始值 value,则初始值为当前本地时间 Date.now()			 * @param {Object} value			 * @param {Object} dateBase			 */			compareValueWithStartAndEnd(value, start, end) {				let winner = null				value = this.superTimeStamp(value)				start = this.superTimeStamp(start)				end = this.superTimeStamp(end)				if (start && end) {					if (value < start) {						winner = new Date(start)					} else if (value > end) {						winner = new Date(end)					} else {						winner = new Date(value)					}				} else if (start && !end) {					winner = start <= value ? new Date(value) : new Date(start)				} else if (!start && end) {					winner = value <= end ? new Date(value) : new Date(end)				} else {					winner = new Date(value)				}				return winner			},			/**			 * 转换为可比较的时间戳,接受日期、时分秒、时间戳			 * @param {Object} value			 */			superTimeStamp(value) {				let dateBase = ''				if (this.type === 'time' && value && typeof value === 'string') {					const now = new Date()					const year = now.getFullYear()					const month = now.getMonth() + 1					const day = now.getDate()					dateBase = year + '/' + month + '/' + day + ' '				}				if (Number(value) && typeof value !== NaN) {					value = parseInt(value)					dateBase = 0				}				return this.createTimeStamp(dateBase + value)			},			/**			 * 解析默认值 value,字符串、时间戳			 * @param {Object} defaultTime			 */			parseValue(value) {				if (!value) {					return				}				if (this.type === 'time' && typeof value === "string") {					this.parseTimeType(value)				} else {					let defaultDate = null					defaultDate = new Date(value)					if (this.type !== 'time') {						this.year = defaultDate.getFullYear()						this.month = defaultDate.getMonth() + 1						this.day = defaultDate.getDate()					}					if (this.type !== 'date') {						this.hour = defaultDate.getHours()						this.minute = defaultDate.getMinutes()						this.second = defaultDate.getSeconds()					}				}				if (this.hideSecond) {					this.second = 0				}			},			/**			 * 解析可选择时间范围 start、end,年月日字符串、时间戳			 * @param {Object} defaultTime			 */			parseDatetimeRange(point, pointType) {				// 时间为空,则重置为初始值				if (!point) {					if (pointType === 'start') {						this.startYear = 1920						this.startMonth = 1						this.startDay = 1						this.startHour = 0						this.startMinute = 0						this.startSecond = 0					}					if (pointType === 'end') {						this.endYear = 2120						this.endMonth = 12						this.endDay = 31						this.endHour = 23						this.endMinute = 59						this.endSecond = 59					}					return				}				if (this.type === 'time') {					const pointArr = point.split(':')					this[pointType + 'Hour'] = Number(pointArr[0])					this[pointType + 'Minute'] = Number(pointArr[1])					this[pointType + 'Second'] = Number(pointArr[2])				} else {					if (!point) {						pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60						return					}					if (Number(point) && Number(point) !== NaN) {						point = parseInt(point)					}					// datetime 的 end 没有时分秒, 则不限制					const hasTime = /[0-9]:[0-9]/					if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(							point)) {						point = point + ' 23:59:59'					}					const pointDate = new Date(point)					this[pointType + 'Year'] = pointDate.getFullYear()					this[pointType + 'Month'] = pointDate.getMonth() + 1					this[pointType + 'Day'] = pointDate.getDate()					if (this.type === 'datetime') {						this[pointType + 'Hour'] = pointDate.getHours()						this[pointType + 'Minute'] = pointDate.getMinutes()						this[pointType + 'Second'] = pointDate.getSeconds()					}				}			},			// 获取 年、月、日、时、分、秒 当前可选范围			getCurrentRange(value) {				const range = []				for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {					range.push(i)				}				return range			},			// 字符串首字母大写			capitalize(str) {				return str.charAt(0).toUpperCase() + str.slice(1)			},			// 检查当前值是否在范围内,不在则当前值重置为可选范围第一项			checkValue(name, value, values) {				if (values.indexOf(value) === -1) {					this[name] = values[0]				}			},			// 每个月的实际天数			daysInMonth(year, month) { // Use 1 for January, 2 for February, etc.				return new Date(year, month, 0).getDate();			},			//兼容 iOS、safari 日期格式			fixIosDateFormat(value) {				if (typeof value === 'string') {					value = value.replace(/-/g, '/')				}				return value			},			/**			 * 生成时间戳			 * @param {Object} time			 */			createTimeStamp(time) {				if (!time) return				if (typeof time === "number") {					return time				} else {					time = time.replace(/-/g, '/')					if (this.type === 'date') {						time = time + ' ' + '00:00:00'					}					return Date.parse(time)				}			},			/**			 * 生成日期或时间的字符串			 */			createDomSting() {				const yymmdd = this.year +					'-' +					this.lessThanTen(this.month) +					'-' +					this.lessThanTen(this.day)				let hhmmss = this.lessThanTen(this.hour) +					':' +					this.lessThanTen(this.minute)				if (!this.hideSecond) {					hhmmss = hhmmss + ':' + this.lessThanTen(this.second)				}				if (this.type === 'date') {					return yymmdd				} else if (this.type === 'time') {					return hhmmss				} else {					return yymmdd + ' ' + hhmmss				}			},			/**			 * 初始化返回值,并抛出 change 事件			 */			initTime(emit = true) {				this.time = this.createDomSting()				if (!emit) return				if (this.returnType === 'timestamp' && this.type !== 'time') {					this.$emit('change', this.createTimeStamp(this.time))					this.$emit('input', this.createTimeStamp(this.time))					this.$emit('update:modelValue', this.createTimeStamp(this.time))				} else {					this.$emit('change', this.time)					this.$emit('input', this.time)					this.$emit('update:modelValue', this.time)				}			},			/**			 * 用户选择日期或时间更新 data			 * @param {Object} e			 */			bindDateChange(e) {				const val = e.detail.value				this.year = this.years[val[0]]				this.month = this.months[val[1]]				this.day = this.days[val[2]]			},			bindTimeChange(e) {				const val = e.detail.value				this.hour = this.hours[val[0]]				this.minute = this.minutes[val[1]]				this.second = this.seconds[val[2]]			},			/**			 * 初始化弹出层			 */			initTimePicker() {				if (this.disabled) return				const value = this.fixIosDateFormat(this.value)				this.initPickerValue(value)				this.visible = !this.visible			},			/**			 * 触发或关闭弹框			 */			tiggerTimePicker(e) {				this.visible = !this.visible			},			/**			 * 用户点击“清空”按钮,清空当前值			 */			clearTime() {				this.time = ''				this.$emit('change', this.time)				this.$emit('input', this.time)				this.$emit('update:modelValue', this.time)				this.tiggerTimePicker()			},			/**			 * 用户点击“确定”按钮			 */			setTime() {				this.initTime()				this.tiggerTimePicker()			}		}	}</script><style>	.uni-datetime-picker {		/* #ifndef APP-NVUE */		/* width: 100%; */		/* #endif */	}	.uni-datetime-picker-view {		height: 130px;		width: 270px;		/* #ifndef APP-NVUE */		cursor: pointer;		/* #endif */	}	.uni-datetime-picker-item {		height: 50px;		line-height: 50px;		text-align: center;		font-size: 14px;	}	.uni-datetime-picker-btn {		margin-top: 60px;		/* #ifndef APP-NVUE */		display: flex;		cursor: pointer;		/* #endif */		flex-direction: row;		justify-content: space-between;	}	.uni-datetime-picker-btn-text {		font-size: 14px;		color: #007AFF;	}	.uni-datetime-picker-btn-group {		/* #ifndef APP-NVUE */		display: flex;		/* #endif */		flex-direction: row;	}	.uni-datetime-picker-cancel {		margin-right: 30px;	}	.uni-datetime-picker-mask {		position: fixed;		bottom: 0px;		top: 0px;		left: 0px;		right: 0px;		background-color: rgba(0, 0, 0, 0.4);		transition-duration: 0.3s;		z-index: 998;	}	.uni-datetime-picker-popup {		border-radius: 8px;		padding: 30px;		width: 270px;		/* #ifdef APP-NVUE */		height: 500px;		/* #endif */		/* #ifdef APP-NVUE */		width: 330px;		/* #endif */		background-color: #fff;		position: fixed;		top: 50%;		left: 50%;		transform: translate(-50%, -50%);		transition-duration: 0.3s;		z-index: 999;	}	.fix-nvue-height {		/* #ifdef APP-NVUE */		height: 330px;		/* #endif */	}	.uni-datetime-picker-time {		color: grey;	}	.uni-datetime-picker-column {		height: 50px;	}	.uni-datetime-picker-timebox {		border: 1px solid #E5E5E5;		border-radius: 5px;		padding: 7px 10px;		/* #ifndef APP-NVUE */		box-sizing: border-box;		cursor: pointer;		/* #endif */	}	.uni-datetime-picker-timebox-pointer {		/* #ifndef APP-NVUE */		cursor: pointer;		/* #endif */	}	.uni-datetime-picker-disabled {		opacity: 0.4;		/* #ifdef H5 */		cursor: not-allowed !important;		/* #endif */	}	.uni-datetime-picker-text {		font-size: 14px;	}	.uni-datetime-picker-sign {		position: absolute;		top: 53px;		/* 减掉 10px 的元素高度,兼容nvue */		color: #999;		/* #ifdef APP-NVUE */		font-size: 16px;		/* #endif */	}	.sign-left {		left: 86px;	}	.sign-right {		right: 86px;	}	.sign-center {		left: 135px;	}	.uni-datetime-picker__container-box {		position: relative;		display: flex;		align-items: center;		justify-content: center;		margin-top: 40px;	}	.time-hide-second {		width: 180px;	}</style>
 |