1058 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
		
		
			
		
	
	
			1058 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
|  | <template> | |||
|  | 	<view class="uni-date"> | |||
|  | 		<view class="uni-date-editor" @click="show"> | |||
|  | 			<slot> | |||
|  | 				<view class="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="calendarRange.startDate" | |||
|  | 						:end-date="calendarRange.endDate" :range="true" :pleStatus="endMultipleStatus" @change="leftChange" | |||
|  | 						@firstEnterCale="updateRightCale" style="padding: 0 8px;"/> | |||
|  | 					<Calendar ref="right" :showMonth="false" :start-date="calendarRange.startDate" | |||
|  | 						:end-date="calendarRange.endDate" :range="true" @change="rightChange" :pleStatus="startMultipleStatus" | |||
|  | 						@firstEnterCale="updateLeftCale" 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" :range="isRange" :hasTime="hasTime" :insert="false" | |||
|  | 			:hideSecond="hideSecond" @confirm="mobileChange" @maskClose="close" @change="calendarClick"/> | |||
|  | 	</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: { | |||
|  | 			// #ifdef MP-TOUTIAO
 | |||
|  | 			virtualHost: false, | |||
|  | 			// #endif
 | |||
|  | 			// #ifndef MP-TOUTIAO
 | |||
|  | 			virtualHost: true | |||
|  | 			// #endif
 | |||
|  | 		}, | |||
|  | 		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: { | |||
|  | 			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') !== -1 | |||
|  | 					this.isRange = newVal.indexOf('range') !== -1 | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			// #ifndef VUE3
 | |||
|  | 			value: { | |||
|  | 				immediate: true, | |||
|  | 				handler(newVal) { | |||
|  | 					if (this.isEmitValue) { | |||
|  | 						this.isEmitValue = false | |||
|  | 						return | |||
|  | 					} | |||
|  | 					this.initPicker(newVal) | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			// #endif
 | |||
|  | 			// #ifdef VUE3
 | |||
|  | 			modelValue: { | |||
|  | 				immediate: true, | |||
|  | 				handler(newVal) { | |||
|  | 					if (this.isEmitValue) { | |||
|  | 						this.isEmitValue = false | |||
|  | 						return | |||
|  | 					} | |||
|  | 					this.initPicker(newVal) | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			// #endif
 | |||
|  | 			start: { | |||
|  | 				immediate: true, | |||
|  | 				handler(newVal) { | |||
|  | 					if (!newVal) return | |||
|  | 					this.calendarRange.startDate = getDate(newVal) | |||
|  | 					if (this.hasTime) { | |||
|  | 						this.calendarRange.startTime = getTime(newVal) | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			end: { | |||
|  | 				immediate: true, | |||
|  | 				handler(newVal) { | |||
|  | 					if (!newVal) return | |||
|  | 					this.calendarRange.endDate = getDate(newVal) | |||
|  | 					if (this.hasTime) { | |||
|  | 						this.calendarRange.endTime = getTime(newVal, this.hideSecond) | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 		}, | |||
|  | 		computed: { | |||
|  | 			timepickerStartTime() { | |||
|  | 				const activeDate = this.isRange ? this.tempRange.startDate : this.inputDate | |||
|  | 				return activeDate === this.calendarRange.startDate ? this.calendarRange.startTime : '' | |||
|  | 			}, | |||
|  | 			timepickerEndTime() { | |||
|  | 				const activeDate = this.isRange ? this.tempRange.endDate : this.inputDate | |||
|  | 				return 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() { | |||
|  | 				// todo
 | |||
|  | 				return 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] = newVal | |||
|  | 					if (!before && !after) return | |||
|  | 					const beforeDate = getDate(before) | |||
|  | 					const beforeTime = getTime(before, this.hideSecond) | |||
|  | 
 | |||
|  | 					const afterDate = getDate(after) | |||
|  | 					const afterTime = getTime(after, this.hideSecond) | |||
|  | 					const startDate = beforeDate | |||
|  | 					const endDate = afterDate | |||
|  | 					this.displayRangeValue.startDate = this.tempRange.startDate = startDate | |||
|  | 					this.displayRangeValue.endDate = this.tempRange.endDate = endDate | |||
|  | 
 | |||
|  | 					if (this.hasTime) { | |||
|  | 						this.displayRangeValue.startDate = `${beforeDate} ${beforeTime}` | |||
|  | 						this.displayRangeValue.endDate = `${afterDate} ${afterTime}` | |||
|  | 						this.tempRange.startTime = beforeTime | |||
|  | 						this.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') !== -1 | |||
|  | 					return | |||
|  | 				} | |||
|  | 				const { | |||
|  | 					windowWidth | |||
|  | 				} = uni.getSystemInfoSync() | |||
|  | 				this.isPhone = windowWidth <= 500 | |||
|  | 				this.windowWidth = windowWidth | |||
|  | 			}, | |||
|  | 			show() { | |||
|  | 				this.$emit("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.pickerVisible | |||
|  | 					if (!this.isPhone && this.isRange && this.isFirstShow) { | |||
|  | 						this.isFirstShow = false | |||
|  | 						const { | |||
|  | 							startDate, | |||
|  | 							endDate | |||
|  | 						} = this.calendarRange | |||
|  | 						if (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 = false | |||
|  | 					this.$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.fulldate | |||
|  | 				if (this.hasTime) return | |||
|  | 				this.confirmSingleChange() | |||
|  | 			}, | |||
|  | 			confirmSingleChange() { | |||
|  | 				if (!checkDate(this.inputDate)) { | |||
|  | 					const now = new Date() | |||
|  | 					this.calendarDate = this.inputDate = getDate(now) | |||
|  | 					this.pickerTime = getTime(now, this.hideSecond) | |||
|  | 				} | |||
|  | 
 | |||
|  | 				let startLaterInputDate = false | |||
|  | 				let startDate, startTime | |||
|  | 				if (this.start) { | |||
|  | 					let startString = this.start | |||
|  | 					if (typeof this.start === 'number') { | |||
|  | 						startString = getDateTime(this.start, this.hideSecond) | |||
|  | 					} | |||
|  | 					[startDate, startTime] = startString.split(' ') | |||
|  | 					if (this.start && !dateCompare(startDate, this.inputDate)) { | |||
|  | 						startLaterInputDate = true | |||
|  | 						this.inputDate = startDate | |||
|  | 					} | |||
|  | 				} | |||
|  | 
 | |||
|  | 				let endEarlierInputDate = false | |||
|  | 				let endDate, endTime | |||
|  | 				if (this.end) { | |||
|  | 					let endString = this.end | |||
|  | 					if (typeof this.end === 'number') { | |||
|  | 						endString = getDateTime(this.end, this.hideSecond) | |||
|  | 					} | |||
|  | 					[endDate, endTime] = endString.split(' ') | |||
|  | 					if (this.end && !dateCompare(this.inputDate, endDate)) { | |||
|  | 						endEarlierInputDate = true | |||
|  | 						this.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.range | |||
|  | 				this.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) | |||
|  | 				this.$emit('calendarClick', e) | |||
|  | 			}, | |||
|  | 			rightChange(e) { | |||
|  | 				const { | |||
|  | 					before, | |||
|  | 					after | |||
|  | 				} = e.range | |||
|  | 				this.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) | |||
|  | 				this.$emit('calendarClick', e) | |||
|  | 			}, | |||
|  | 			mobileChange(e) { | |||
|  | 				if (this.isRange) { | |||
|  | 					const { | |||
|  | 						before, | |||
|  | 						after | |||
|  | 					} = e.range | |||
|  | 					if (!before) { | |||
|  | 						return; | |||
|  | 					} | |||
|  | 
 | |||
|  | 					this.handleStartAndEnd(before, after, true) | |||
|  | 					if (this.hasTime) { | |||
|  | 						const { | |||
|  | 							startTime, | |||
|  | 							endTime | |||
|  | 						} = e.timeRange | |||
|  | 						this.tempRange.startTime = startTime | |||
|  | 						this.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)) return | |||
|  | 				this.handleStartAndEnd(before, after, true) | |||
|  | 				if (this.hasTime) return | |||
|  | 				this.confirmRangeChange() | |||
|  | 			}, | |||
|  | 			confirmRangeChange() { | |||
|  | 				if (!this.tempRange.startDate || !this.tempRange.endDate) { | |||
|  | 					this.pickerVisible = false | |||
|  | 					return | |||
|  | 				} | |||
|  | 				if (!checkDate(this.tempRange.startDate)) { | |||
|  | 					this.tempRange.startDate = getDate(Date.now()) | |||
|  | 				} | |||
|  | 				if (!checkDate(this.tempRange.endDate)) { | |||
|  | 					this.tempRange.endDate = getDate(Date.now()) | |||
|  | 				} | |||
|  | 
 | |||
|  | 				let start, end | |||
|  | 
 | |||
|  | 				let startDateLaterRangeStartDate = false | |||
|  | 				let startDateLaterRangeEndDate = false | |||
|  | 				let startDate, startTime | |||
|  | 				if (this.start) { | |||
|  | 					let startString = this.start | |||
|  | 					if (typeof this.start === 'number') { | |||
|  | 						startString = getDateTime(this.start, this.hideSecond) | |||
|  | 					} | |||
|  | 					[startDate, startTime] = startString.split(' ') | |||
|  | 					if (this.start && !dateCompare(this.start, this.tempRange.startDate)) { | |||
|  | 						startDateLaterRangeStartDate = true | |||
|  | 						this.tempRange.startDate = startDate | |||
|  | 					} | |||
|  | 					if (this.start && !dateCompare(this.start, this.tempRange.endDate)) { | |||
|  | 						startDateLaterRangeEndDate = true | |||
|  | 						this.tempRange.endDate = startDate | |||
|  | 					} | |||
|  | 				} | |||
|  | 				let endDateEarlierRangeStartDate = false | |||
|  | 				let endDateEarlierRangeEndDate = false | |||
|  | 				let endDate, endTime | |||
|  | 				if (this.end) { | |||
|  | 					let endString = this.end | |||
|  | 					if (typeof this.end === 'number') { | |||
|  | 						endString = getDateTime(this.end, this.hideSecond) | |||
|  | 					} | |||
|  | 					[endDate, endTime] = endString.split(' ') | |||
|  | 
 | |||
|  | 					if (this.end && !dateCompare(this.tempRange.startDate, this.end)) { | |||
|  | 						endDateEarlierRangeStartDate = true | |||
|  | 						this.tempRange.startDate = endDate | |||
|  | 					} | |||
|  | 					if (this.end && !dateCompare(this.tempRange.endDate, this.end)) { | |||
|  | 						endDateEarlierRangeEndDate = true | |||
|  | 						this.tempRange.endDate = endDate | |||
|  | 					} | |||
|  | 				} | |||
|  | 				if (!this.hasTime) { | |||
|  | 					start = this.displayRangeValue.startDate = this.tempRange.startDate | |||
|  | 					end = 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 = start | |||
|  | 				this.displayRangeValue.endDate = end | |||
|  | 				const displayRange = [start, end] | |||
|  | 				this.setEmit(displayRange) | |||
|  | 				this.pickerVisible = false | |||
|  | 			}, | |||
|  | 			handleStartAndEnd(before, after, temp = false) { | |||
|  | 				if (!before) return | |||
|  | 				if (!after) after = before; | |||
|  | 				const type = temp ? 'tempRange' : 'range' | |||
|  | 				const isStartEarlierEnd = dateCompare(before, after) | |||
|  | 				this[type].startDate = isStartEarlierEnd ? before : after | |||
|  | 				this[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', []) | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 
 | |||
|  | 			calendarClick(e) { | |||
|  | 				this.$emit('calendarClick', e) | |||
|  | 			} | |||
|  | 		} | |||
|  | 	} | |||
|  | </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> |