uni-datetime-picker.vue 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. <template>
  2. <view class="uni-date">
  3. <view class="uni-date-editor" @click="show">
  4. <slot>
  5. <view
  6. class="uni-date-editor--x"
  7. :class="{'uni-date-editor--x__disabled': disabled,'uni-date-x--border': border}"
  8. >
  9. <view v-if="!isRange" class="uni-date-x uni-date-single">
  10. <uni-icons v-if="showCalendarIcon" class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons>
  11. <view class="uni-date__x-input">{{ displayValue || singlePlaceholderText }}</view>
  12. </view>
  13. <view v-else class="uni-date-x uni-date-range">
  14. <uni-icons class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons>
  15. <view class="uni-date__x-input text-center">{{ displayRangeValue.startDate || startPlaceholderText }}</view>
  16. <view class="range-separator">{{rangeSeparator}}</view>
  17. <view class="uni-date__x-input text-center">{{ displayRangeValue.endDate || endPlaceholderText }}</view>
  18. </view>
  19. <view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear">
  20. <uni-icons type="clear" color="#c0c4cc" size="22"></uni-icons>
  21. </view>
  22. </view>
  23. </slot>
  24. </view>
  25. <view v-show="pickerVisible" class="uni-date-mask--pc" @click="close"></view>
  26. <view v-if="!isPhone" v-show="pickerVisible" ref="datePicker" class="uni-date-picker__container">
  27. <view v-if="!isRange" class="uni-date-single--x" :style="pickerPositionStyle">
  28. <view class="uni-popper__arrow"></view>
  29. <view v-if="hasTime" class="uni-date-changed popup-x-header">
  30. <input class="uni-date__input text-center" type="text" v-model="inputDate"
  31. :placeholder="selectDateText" />
  32. <time-picker type="time" v-model="pickerTime" :border="false" :disabled="!inputDate"
  33. :start="timepickerStartTime" :end="timepickerEndTime" :hideSecond="hideSecond" style="width: 100%;">
  34. <input class="uni-date__input text-center" type="text" v-model="pickerTime" :placeholder="selectTimeText"
  35. :disabled="!inputDate" />
  36. </time-picker>
  37. </view>
  38. <Calendar ref="pcSingle" :showMonth="false" :start-date="calendarRange.startDate"
  39. :end-date="calendarRange.endDate" :date="calendarDate" @change="singleChange"
  40. :default-value="defaultValue"
  41. style="padding: 0 8px;" />
  42. <view v-if="hasTime" class="popup-x-footer">
  43. <text class="confirm-text" @click="confirmSingleChange">{{okText}}</text>
  44. </view>
  45. </view>
  46. <view v-else class="uni-date-range--x" :style="pickerPositionStyle">
  47. <view class="uni-popper__arrow"></view>
  48. <view v-if="hasTime" class="popup-x-header uni-date-changed">
  49. <view class="popup-x-header--datetime">
  50. <input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.startDate"
  51. :placeholder="startDateText" />
  52. <time-picker type="time" v-model="tempRange.startTime" :start="timepickerStartTime" :border="false"
  53. :disabled="!tempRange.startDate" :hideSecond="hideSecond">
  54. <input class="uni-date__input uni-date-range__input" type="text"
  55. v-model="tempRange.startTime" :placeholder="startTimeText"
  56. :disabled="!tempRange.startDate" />
  57. </time-picker>
  58. </view>
  59. <uni-icons type="arrowthinright" color="#999" style="line-height: 40px;"></uni-icons>
  60. <view class="popup-x-header--datetime">
  61. <input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endDate"
  62. :placeholder="endDateText" />
  63. <time-picker type="time" v-model="tempRange.endTime" :end="timepickerEndTime" :border="false"
  64. :disabled="!tempRange.endDate" :hideSecond="hideSecond">
  65. <input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endTime"
  66. :placeholder="endTimeText" :disabled="!tempRange.endDate" />
  67. </time-picker>
  68. </view>
  69. </view>
  70. <view class="popup-x-body">
  71. <Calendar ref="left" :showMonth="false" :start-date="calendarRange.startDate"
  72. :end-date="calendarRange.endDate" :range="true" :pleStatus="endMultipleStatus"
  73. @change="leftChange" @firstEnterCale="updateRightCale" style="padding: 0 8px;" />
  74. <Calendar ref="right" :showMonth="false" :start-date="calendarRange.startDate"
  75. :end-date="calendarRange.endDate" :range="true" @change="rightChange"
  76. :pleStatus="startMultipleStatus" @firstEnterCale="updateLeftCale"
  77. style="padding: 0 8px;border-left: 1px solid #F1F1F1;" />
  78. </view>
  79. <view v-if="hasTime" class="popup-x-footer">
  80. <text @click="clear">{{clearText}}</text>
  81. <text class="confirm-text" @click="confirmRangeChange">{{okText}}</text>
  82. </view>
  83. </view>
  84. </view>
  85. <Calendar v-if="isPhone" ref="mobile" :clearDate="false" :date="calendarDate" :defTime="mobileCalendarTime"
  86. :start-date="calendarRange.startDate" :end-date="calendarRange.endDate" :selectableTimes="mobSelectableTime"
  87. :startPlaceholder="startPlaceholder" :endPlaceholder="endPlaceholder"
  88. :default-value="defaultValue"
  89. :pleStatus="endMultipleStatus" :showMonth="false" :range="isRange" :hasTime="hasTime" :insert="false"
  90. :hideSecond="hideSecond" @confirm="mobileChange" @maskClose="close" @changeStatus='(status) => $emit("changeStatus", status)' />
  91. </view>
  92. </template>
  93. <script>
  94. /**
  95. * DatetimePicker 时间选择器
  96. * @description 同时支持 PC 和移动端使用日历选择日期和日期范围
  97. * @tutorial https://ext.dcloud.net.cn/plugin?id=3962
  98. * @property {String} type 选择器类型
  99. * @property {String|Number|Array|Date} value 绑定值
  100. * @property {String} placeholder 单选择时的占位内容
  101. * @property {String} start 起始时间
  102. * @property {String} end 终止时间
  103. * @property {String} start-placeholder 范围选择时开始日期的占位内容
  104. * @property {String} end-placeholder 范围选择时结束日期的占位内容
  105. * @property {String} range-separator 选择范围时的分隔符
  106. * @property {Boolean} border = [true|false] 是否有边框
  107. * @property {Boolean} disabled = [true|false] 是否禁用
  108. * @property {Boolean} clearIcon = [true|false] 是否显示清除按钮(仅PC端适用)
  109. * @property {Boolean} calendarIcon = [true|false] 是否显示前面日历
  110. * @property {[String} defaultValue 选择器打开时默认显示的时间
  111. * @event {Function} change 确定日期时触发的事件
  112. * @event {Function} maskClick 点击遮罩层触发的事件
  113. * @event {Function} show 打开弹出层
  114. * @event {Function} close 关闭弹出层
  115. * @event {Function} clear 清除上次选中的状态和值
  116. **/
  117. import Calendar from './calendar.vue'
  118. import TimePicker from './time-picker.vue'
  119. import { initVueI18n } from '@dcloudio/uni-i18n'
  120. import i18nMessages from './i18n/index.js'
  121. import { getDateTime, getDate, getTime, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat } from './util'
  122. export default {
  123. name: 'UniDatetimePicker',
  124. options: {
  125. virtualHost: true
  126. },
  127. components: {
  128. Calendar,
  129. TimePicker
  130. },
  131. data() {
  132. return {
  133. isRange: false,
  134. hasTime: false,
  135. displayValue: '',
  136. inputDate: '',
  137. calendarDate: '',
  138. pickerTime: '',
  139. calendarRange: {
  140. startDate: '',
  141. startTime: '',
  142. endDate: '',
  143. endTime: ''
  144. },
  145. displayRangeValue: {
  146. startDate: '',
  147. endDate: '',
  148. },
  149. tempRange: {
  150. startDate: '',
  151. startTime: '',
  152. endDate: '',
  153. endTime: ''
  154. },
  155. // 左右日历同步数据
  156. startMultipleStatus: {
  157. before: '',
  158. after: '',
  159. data: [],
  160. fulldate: ''
  161. },
  162. endMultipleStatus: {
  163. before: '',
  164. after: '',
  165. data: [],
  166. fulldate: ''
  167. },
  168. pickerVisible: false,
  169. pickerPositionStyle: null,
  170. isEmitValue: false,
  171. isPhone: false,
  172. isFirstShow: true,
  173. i18nT: () => {}
  174. }
  175. },
  176. props: {
  177. type: {
  178. type: String,
  179. default: 'datetime'
  180. },
  181. value: {
  182. type: [String, Number, Array, Date],
  183. default: ''
  184. },
  185. modelValue: {
  186. type: [String, Number, Array, Date],
  187. default: ''
  188. },
  189. start: {
  190. type: [Number, String],
  191. default: ''
  192. },
  193. end: {
  194. type: [Number, String],
  195. default: ''
  196. },
  197. returnType: {
  198. type: String,
  199. default: 'string'
  200. },
  201. placeholder: {
  202. type: String,
  203. default: ''
  204. },
  205. startPlaceholder: {
  206. type: String,
  207. default: ''
  208. },
  209. endPlaceholder: {
  210. type: String,
  211. default: ''
  212. },
  213. rangeSeparator: {
  214. type: String,
  215. default: '-'
  216. },
  217. border: {
  218. type: [Boolean],
  219. default: true
  220. },
  221. disabled: {
  222. type: [Boolean],
  223. default: false
  224. },
  225. clearIcon: {
  226. type: [Boolean],
  227. default: true
  228. },
  229. calendarIcon: {
  230. type: [Boolean],
  231. default: true
  232. },
  233. hideSecond: {
  234. type: [Boolean],
  235. default: false
  236. },
  237. defaultValue: {
  238. type: [String, Object, Array],
  239. default: ''
  240. }
  241. },
  242. watch: {
  243. type: {
  244. immediate: true,
  245. handler(newVal) {
  246. this.hasTime = newVal.indexOf('time') !== -1
  247. this.isRange = newVal.indexOf('range') !== -1
  248. }
  249. },
  250. // #ifndef VUE3
  251. value: {
  252. immediate: true,
  253. handler(newVal) {
  254. if (this.isEmitValue) {
  255. this.isEmitValue = false
  256. return
  257. }
  258. this.initPicker(newVal)
  259. }
  260. },
  261. // #endif
  262. // #ifdef VUE3
  263. modelValue: {
  264. immediate: true,
  265. handler(newVal) {
  266. if (this.isEmitValue) {
  267. this.isEmitValue = false
  268. return
  269. }
  270. this.initPicker(newVal)
  271. }
  272. },
  273. // #endif
  274. start: {
  275. immediate: true,
  276. handler(newVal) {
  277. if (!newVal) return
  278. this.calendarRange.startDate = getDate(newVal)
  279. if (this.hasTime) {
  280. this.calendarRange.startTime = getTime(newVal)
  281. }
  282. }
  283. },
  284. end: {
  285. immediate: true,
  286. handler(newVal) {
  287. if (!newVal) return
  288. this.calendarRange.endDate = getDate(newVal)
  289. if (this.hasTime) {
  290. this.calendarRange.endTime = getTime(newVal, this.hideSecond)
  291. }
  292. }
  293. },
  294. },
  295. computed: {
  296. timepickerStartTime() {
  297. const activeDate = this.isRange ? this.tempRange.startDate : this.inputDate
  298. return activeDate === this.calendarRange.startDate ? this.calendarRange.startTime : ''
  299. },
  300. timepickerEndTime() {
  301. const activeDate = this.isRange ? this.tempRange.endDate : this.inputDate
  302. return activeDate === this.calendarRange.endDate ? this.calendarRange.endTime : ''
  303. },
  304. mobileCalendarTime() {
  305. const timeRange = {
  306. start: this.tempRange.startTime,
  307. end: this.tempRange.endTime
  308. }
  309. return this.isRange ? timeRange : this.pickerTime
  310. },
  311. mobSelectableTime() {
  312. return {
  313. start: this.calendarRange.startTime,
  314. end: this.calendarRange.endTime
  315. }
  316. },
  317. datePopupWidth() {
  318. // todo
  319. return this.isRange ? 653 : 301
  320. },
  321. /**
  322. * for i18n
  323. */
  324. singlePlaceholderText() {
  325. return this.placeholder || (this.type === 'date' ? this.selectDateText : this.selectDateTimeText)
  326. },
  327. startPlaceholderText() {
  328. return this.startPlaceholder || this.startDateText
  329. },
  330. endPlaceholderText() {
  331. return this.endPlaceholder || this.endDateText
  332. },
  333. selectDateText() {
  334. return this.i18nT("uni-datetime-picker.selectDate")
  335. },
  336. selectDateTimeText() {
  337. return this.i18nT("uni-datetime-picker.selectDateTime")
  338. },
  339. selectTimeText() {
  340. return this.i18nT("uni-datetime-picker.selectTime")
  341. },
  342. startDateText() {
  343. return this.startPlaceholder || this.i18nT("uni-datetime-picker.startDate")
  344. },
  345. startTimeText() {
  346. return this.i18nT("uni-datetime-picker.startTime")
  347. },
  348. endDateText() {
  349. return this.endPlaceholder || this.i18nT("uni-datetime-picker.endDate")
  350. },
  351. endTimeText() {
  352. return this.i18nT("uni-datetime-picker.endTime")
  353. },
  354. okText() {
  355. return this.i18nT("uni-datetime-picker.ok")
  356. },
  357. clearText() {
  358. return this.i18nT("uni-datetime-picker.clear")
  359. },
  360. showClearIcon() {
  361. return this.clearIcon && !this.disabled && (this.displayValue || (this.displayRangeValue.startDate && this.displayRangeValue.endDate))
  362. },
  363. showCalendarIcon() {
  364. return this.calendarIcon
  365. }
  366. },
  367. created() {
  368. this.initI18nT()
  369. this.platform()
  370. },
  371. methods: {
  372. initI18nT() {
  373. const vueI18n = initVueI18n(i18nMessages)
  374. this.i18nT = vueI18n.t
  375. },
  376. initPicker(newVal) {
  377. if ((!newVal && !this.defaultValue) || Array.isArray(newVal) && !newVal.length) {
  378. this.$nextTick(() => {
  379. this.clear(false)
  380. })
  381. return
  382. }
  383. if (!Array.isArray(newVal) && !this.isRange) {
  384. if(newVal){
  385. this.displayValue = this.inputDate = this.calendarDate = getDate(newVal)
  386. if (this.hasTime) {
  387. this.pickerTime = getTime(newVal, this.hideSecond)
  388. this.displayValue = `${this.displayValue} ${this.pickerTime}`
  389. }
  390. }else if(this.defaultValue){
  391. this.inputDate = this.calendarDate = getDate(this.defaultValue)
  392. if(this.hasTime){
  393. this.pickerTime = getTime(this.defaultValue, this.hideSecond)
  394. }
  395. }
  396. } else {
  397. const [before, after] = newVal
  398. if (!before && !after) return
  399. const beforeDate = getDate(before)
  400. const beforeTime = getTime(before, this.hideSecond)
  401. const afterDate = getDate(after)
  402. const afterTime = getTime(after, this.hideSecond)
  403. const startDate = beforeDate
  404. const endDate = afterDate
  405. this.displayRangeValue.startDate = this.tempRange.startDate = startDate
  406. this.displayRangeValue.endDate = this.tempRange.endDate = endDate
  407. if (this.hasTime) {
  408. this.displayRangeValue.startDate = `${beforeDate} ${beforeTime}`
  409. this.displayRangeValue.endDate = `${afterDate} ${afterTime}`
  410. this.tempRange.startTime = beforeTime
  411. this.tempRange.endTime = afterTime
  412. }
  413. const defaultRange = {
  414. before: beforeDate,
  415. after: afterDate
  416. }
  417. this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, {
  418. which: 'right'
  419. })
  420. this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, defaultRange, {
  421. which: 'left'
  422. })
  423. }
  424. },
  425. updateLeftCale(e) {
  426. const left = this.$refs.left
  427. // 设置范围选
  428. left.cale.setHoverMultiple(e.after)
  429. left.setDate(this.$refs.left.nowDate.fullDate)
  430. },
  431. updateRightCale(e) {
  432. const right = this.$refs.right
  433. // 设置范围选
  434. right.cale.setHoverMultiple(e.after)
  435. right.setDate(this.$refs.right.nowDate.fullDate)
  436. },
  437. platform() {
  438. if(typeof navigator !== "undefined"){
  439. this.isPhone = navigator.userAgent.toLowerCase().indexOf('mobile') !== -1
  440. return
  441. }
  442. const { windowWidth } = uni.getSystemInfoSync()
  443. this.isPhone = windowWidth <= 500
  444. this.windowWidth = windowWidth
  445. },
  446. show() {
  447. if (this.disabled) {
  448. return
  449. }
  450. this.platform()
  451. if (this.isPhone) {
  452. setTimeout(() => {
  453. this.$refs.mobile.open()
  454. }, 0);
  455. return
  456. }
  457. this.pickerPositionStyle = {
  458. top: '10px'
  459. }
  460. const dateEditor = uni.createSelectorQuery().in(this).select(".uni-date-editor")
  461. dateEditor.boundingClientRect(rect => {
  462. if (this.windowWidth - rect.left < this.datePopupWidth) {
  463. this.pickerPositionStyle.right = 0
  464. }
  465. }).exec()
  466. setTimeout(() => {
  467. this.pickerVisible = !this.pickerVisible
  468. if (!this.isPhone && this.isRange && this.isFirstShow) {
  469. this.isFirstShow = false
  470. const {
  471. startDate,
  472. endDate
  473. } = this.calendarRange
  474. if (startDate && endDate) {
  475. if (this.diffDate(startDate, endDate) < 30) {
  476. this.$refs.right.changeMonth('pre')
  477. }
  478. } else {
  479. this.$refs.right.changeMonth('next')
  480. this.$refs.right.cale.lastHover = false
  481. }
  482. }
  483. }, 50)
  484. },
  485. close() {
  486. setTimeout(() => {
  487. this.pickerVisible = false
  488. this.$emit('maskClick', this.value)
  489. this.$refs.mobile && this.$refs.mobile.close()
  490. }, 20)
  491. },
  492. setEmit(value) {
  493. if (this.returnType === "timestamp" || this.returnType === "date") {
  494. if (!Array.isArray(value)) {
  495. if (!this.hasTime) {
  496. value = value + ' ' + '00:00:00'
  497. }
  498. value = this.createTimestamp(value)
  499. if (this.returnType === "date") {
  500. value = new Date(value)
  501. }
  502. } else {
  503. if (!this.hasTime) {
  504. value[0] = value[0] + ' ' + '00:00:00'
  505. value[1] = value[1] + ' ' + '00:00:00'
  506. }
  507. value[0] = this.createTimestamp(value[0])
  508. value[1] = this.createTimestamp(value[1])
  509. if (this.returnType === "date") {
  510. value[0] = new Date(value[0])
  511. value[1] = new Date(value[1])
  512. }
  513. }
  514. }
  515. this.$emit('update:modelValue', value)
  516. this.$emit('input', value)
  517. this.$emit('change', value)
  518. this.isEmitValue = true
  519. },
  520. createTimestamp(date) {
  521. date = fixIosDateFormat(date)
  522. return Date.parse(new Date(date))
  523. },
  524. singleChange(e) {
  525. this.calendarDate = this.inputDate = e.fulldate
  526. if (this.hasTime) return
  527. this.confirmSingleChange()
  528. },
  529. confirmSingleChange() {
  530. if(!checkDate(this.inputDate)){
  531. const now = new Date()
  532. this.calendarDate = this.inputDate = getDate(now)
  533. this.pickerTime = getTime(now, this.hideSecond)
  534. }
  535. let startLaterInputDate = false
  536. let startDate, startTime
  537. if(this.start) {
  538. let startString = this.start
  539. if(typeof this.start === 'number'){
  540. startString = getDateTime(this.start, this.hideSecond)
  541. }
  542. [startDate, startTime] = startString.split(' ')
  543. if(this.start && !dateCompare(startDate, this.inputDate)) {
  544. startLaterInputDate = true
  545. this.inputDate = startDate
  546. }
  547. }
  548. let endEarlierInputDate = false
  549. let endDate, endTime
  550. if(this.end) {
  551. let endString = this.end
  552. if(typeof this.end === 'number'){
  553. endString = getDateTime(this.end, this.hideSecond)
  554. }
  555. [endDate, endTime] = endString.split(' ')
  556. if(this.end && !dateCompare(this.inputDate, endDate)) {
  557. endEarlierInputDate = true
  558. this.inputDate = endDate
  559. }
  560. }
  561. if (this.hasTime) {
  562. if(startLaterInputDate){
  563. this.pickerTime = startTime || getDefaultSecond(this.hideSecond)
  564. }
  565. if(endEarlierInputDate){
  566. this.pickerTime = endTime || getDefaultSecond(this.hideSecond)
  567. }
  568. if(!this.pickerTime){
  569. this.pickerTime = getTime(Date.now(), this.hideSecond)
  570. }
  571. this.displayValue = `${this.inputDate} ${this.pickerTime}`
  572. } else {
  573. this.displayValue = this.inputDate
  574. }
  575. this.setEmit(this.displayValue)
  576. this.pickerVisible = false
  577. },
  578. leftChange(e) {
  579. const {
  580. before,
  581. after
  582. } = e.range
  583. this.rangeChange(before, after)
  584. const obj = {
  585. before: e.range.before,
  586. after: e.range.after,
  587. data: e.range.data,
  588. fulldate: e.fulldate
  589. }
  590. this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, obj)
  591. },
  592. rightChange(e) {
  593. const {
  594. before,
  595. after
  596. } = e.range
  597. this.rangeChange(before, after)
  598. const obj = {
  599. before: e.range.before,
  600. after: e.range.after,
  601. data: e.range.data,
  602. fulldate: e.fulldate
  603. }
  604. this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, obj)
  605. },
  606. mobileChange(e) {
  607. if (this.isRange) {
  608. const {before, after} = e.range
  609. if(!before || !after){
  610. return
  611. }
  612. this.handleStartAndEnd(before, after, true)
  613. if (this.hasTime) {
  614. const {
  615. startTime,
  616. endTime
  617. } = e.timeRange
  618. this.tempRange.startTime = startTime
  619. this.tempRange.endTime = endTime
  620. }
  621. this.confirmRangeChange()
  622. } else {
  623. if (this.hasTime) {
  624. this.displayValue = e.fulldate + ' ' + e.time
  625. } else {
  626. this.displayValue = e.fulldate
  627. }
  628. this.setEmit(this.displayValue)
  629. }
  630. this.$refs.mobile.close()
  631. },
  632. rangeChange(before, after) {
  633. if (!(before && after)) return
  634. this.handleStartAndEnd(before, after, true)
  635. if (this.hasTime) return
  636. this.confirmRangeChange()
  637. },
  638. confirmRangeChange() {
  639. if (!this.tempRange.startDate || !this.tempRange.endDate) {
  640. this.pickerVisible = false
  641. return
  642. }
  643. if(!checkDate(this.tempRange.startDate)){
  644. this.tempRange.startDate = getDate(Date.now())
  645. }
  646. if(!checkDate(this.tempRange.endDate)){
  647. this.tempRange.endDate = getDate(Date.now())
  648. }
  649. let start, end
  650. let startDateLaterRangeStartDate = false
  651. let startDateLaterRangeEndDate = false
  652. let startDate, startTime
  653. if(this.start) {
  654. let startString = this.start
  655. if(typeof this.start === 'number'){
  656. startString = getDateTime(this.start, this.hideSecond)
  657. }
  658. [startDate,startTime] = startString.split(' ')
  659. if(this.start && !dateCompare(this.start, this.tempRange.startDate)) {
  660. startDateLaterRangeStartDate = true
  661. this.tempRange.startDate = startDate
  662. }
  663. if(this.start && !dateCompare(this.start, this.tempRange.endDate)) {
  664. startDateLaterRangeEndDate = true
  665. this.tempRange.endDate = startDate
  666. }
  667. }
  668. let endDateEarlierRangeStartDate = false
  669. let endDateEarlierRangeEndDate = false
  670. let endDate, endTime
  671. if(this.end) {
  672. let endString = this.end
  673. if(typeof this.end === 'number'){
  674. endString = getDateTime(this.end, this.hideSecond)
  675. }
  676. [endDate,endTime] = endString.split(' ')
  677. if(this.end && !dateCompare(this.tempRange.startDate, this.end)) {
  678. endDateEarlierRangeStartDate = true
  679. this.tempRange.startDate = endDate
  680. }
  681. if(this.end && !dateCompare(this.tempRange.endDate, this.end)) {
  682. endDateEarlierRangeEndDate = true
  683. this.tempRange.endDate = endDate
  684. }
  685. }
  686. if (!this.hasTime) {
  687. start = this.displayRangeValue.startDate = this.tempRange.startDate
  688. end = this.displayRangeValue.endDate = this.tempRange.endDate
  689. } else {
  690. if(startDateLaterRangeStartDate){
  691. this.tempRange.startTime = startTime || getDefaultSecond(this.hideSecond)
  692. }else if(endDateEarlierRangeStartDate){
  693. this.tempRange.startTime = endTime || getDefaultSecond(this.hideSecond)
  694. }
  695. if(!this.tempRange.startTime){
  696. this.tempRange.startTime = getTime(Date.now(), this.hideSecond)
  697. }
  698. if(startDateLaterRangeEndDate){
  699. this.tempRange.endTime = startTime || getDefaultSecond(this.hideSecond)
  700. }else if(endDateEarlierRangeEndDate){
  701. this.tempRange.endTime = endTime || getDefaultSecond(this.hideSecond)
  702. }
  703. if(!this.tempRange.endTime){
  704. this.tempRange.endTime = getTime(Date.now(), this.hideSecond)
  705. }
  706. start = this.displayRangeValue.startDate = `${this.tempRange.startDate} ${this.tempRange.startTime}`
  707. end = this.displayRangeValue.endDate = `${this.tempRange.endDate} ${this.tempRange.endTime}`
  708. }
  709. if(!dateCompare(start,end)){
  710. [start, end] = [end, start]
  711. }
  712. this.displayRangeValue.startDate = start
  713. this.displayRangeValue.endDate = end
  714. const displayRange = [start, end]
  715. this.setEmit(displayRange)
  716. this.pickerVisible = false
  717. },
  718. handleStartAndEnd(before, after, temp = false) {
  719. if (!(before && after)) return
  720. const type = temp ? 'tempRange' : 'range'
  721. const isStartEarlierEnd = dateCompare(before, after)
  722. this[type].startDate = isStartEarlierEnd ? before : after
  723. this[type].endDate = isStartEarlierEnd ? after : before
  724. },
  725. /**
  726. * 比较时间大小
  727. */
  728. dateCompare(startDate, endDate) {
  729. // 计算截止时间
  730. startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
  731. // 计算详细项的截止时间
  732. endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
  733. return startDate <= endDate
  734. },
  735. /**
  736. * 比较时间差
  737. */
  738. diffDate(startDate, endDate) {
  739. // 计算截止时间
  740. startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
  741. // 计算详细项的截止时间
  742. endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
  743. const diff = (endDate - startDate) / (24 * 60 * 60 * 1000)
  744. return Math.abs(diff)
  745. },
  746. clear(needEmit = true) {
  747. if (!this.isRange) {
  748. this.displayValue = ''
  749. this.inputDate = ''
  750. this.pickerTime = ''
  751. if (this.isPhone) {
  752. this.$refs.mobile && this.$refs.mobile.clearCalender()
  753. } else {
  754. this.$refs.pcSingle && this.$refs.pcSingle.clearCalender()
  755. }
  756. if (needEmit) {
  757. this.$emit('change', '')
  758. this.$emit('input', '')
  759. this.$emit('update:modelValue', '')
  760. }
  761. } else {
  762. this.displayRangeValue.startDate = ''
  763. this.displayRangeValue.endDate = ''
  764. this.tempRange.startDate = ''
  765. this.tempRange.startTime = ''
  766. this.tempRange.endDate = ''
  767. this.tempRange.endTime = ''
  768. if (this.isPhone) {
  769. this.$refs.mobile && this.$refs.mobile.clearCalender()
  770. } else {
  771. this.$refs.left && this.$refs.left.clearCalender()
  772. this.$refs.right && this.$refs.right.clearCalender()
  773. this.$refs.right && this.$refs.right.changeMonth('next')
  774. }
  775. if (needEmit) {
  776. this.$emit('change', [])
  777. this.$emit('input', [])
  778. this.$emit('update:modelValue', [])
  779. }
  780. }
  781. }
  782. }
  783. }
  784. </script>
  785. <style lang="scss">
  786. $uni-primary: #007aff !default;
  787. .uni-date {
  788. width: 100%;
  789. flex: 1;
  790. }
  791. .uni-date-x {
  792. display: flex;
  793. flex-direction: row;
  794. align-items: center;
  795. justify-content: center;
  796. border-radius: 4px;
  797. background-color: #fff;
  798. color: #666;
  799. font-size: 14px;
  800. flex: 1;
  801. .icon-calendar{
  802. padding-left: 3px;
  803. }
  804. .range-separator{
  805. height: 35px;
  806. /* #ifndef MP */
  807. padding: 0 2px;
  808. /* #endif */
  809. line-height: 35px;
  810. }
  811. }
  812. .uni-date-x--border {
  813. box-sizing: border-box;
  814. border-radius: 4px;
  815. border: 1px solid #e5e5e5;
  816. }
  817. .uni-date-editor--x {
  818. display: flex;
  819. align-items: center;
  820. position: relative;
  821. }
  822. .uni-date-editor--x .uni-date__icon-clear {
  823. padding-right: 3px;
  824. display: flex;
  825. align-items: center;
  826. /* #ifdef H5 */
  827. cursor: pointer;
  828. /* #endif */
  829. }
  830. .uni-date__x-input {
  831. width: auto;
  832. height: 35px;
  833. /* #ifndef MP */
  834. padding-left: 5px;
  835. /* #endif */
  836. position: relative;
  837. flex: 1;
  838. line-height: 35px;
  839. font-size: 14px;
  840. overflow: hidden;
  841. }
  842. .text-center {
  843. text-align: center;
  844. }
  845. .uni-date__input {
  846. height: 40px;
  847. width: 100%;
  848. line-height: 40px;
  849. font-size: 14px;
  850. }
  851. .uni-date-range__input {
  852. text-align: center;
  853. max-width: 142px;
  854. }
  855. .uni-date-picker__container {
  856. position: relative;
  857. }
  858. .uni-date-mask--pc {
  859. position: fixed;
  860. bottom: 0px;
  861. top: 0px;
  862. left: 0px;
  863. right: 0px;
  864. background-color: rgba(0, 0, 0, 0);
  865. transition-duration: 0.3s;
  866. z-index: 996;
  867. }
  868. .uni-date-single--x {
  869. background-color: #fff;
  870. position: absolute;
  871. top: 0;
  872. z-index: 999;
  873. border: 1px solid #EBEEF5;
  874. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  875. border-radius: 4px;
  876. }
  877. .uni-date-range--x {
  878. background-color: #fff;
  879. position: absolute;
  880. top: 0;
  881. z-index: 999;
  882. border: 1px solid #EBEEF5;
  883. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  884. border-radius: 4px;
  885. }
  886. .uni-date-editor--x__disabled {
  887. opacity: 0.4;
  888. cursor: default;
  889. }
  890. .uni-date-editor--logo {
  891. width: 16px;
  892. height: 16px;
  893. vertical-align: middle;
  894. }
  895. /* 添加时间 */
  896. .popup-x-header {
  897. /* #ifndef APP-NVUE */
  898. display: flex;
  899. /* #endif */
  900. flex-direction: row;
  901. }
  902. .popup-x-header--datetime {
  903. /* #ifndef APP-NVUE */
  904. display: flex;
  905. /* #endif */
  906. flex-direction: row;
  907. flex: 1;
  908. }
  909. .popup-x-body {
  910. display: flex;
  911. }
  912. .popup-x-footer {
  913. padding: 0 15px;
  914. border-top-color: #F1F1F1;
  915. border-top-style: solid;
  916. border-top-width: 1px;
  917. line-height: 40px;
  918. text-align: right;
  919. color: #666;
  920. }
  921. .popup-x-footer text:hover {
  922. color: $uni-primary;
  923. cursor: pointer;
  924. opacity: 0.8;
  925. }
  926. .popup-x-footer .confirm-text {
  927. margin-left: 20px;
  928. color: $uni-primary;
  929. }
  930. .uni-date-changed {
  931. text-align: center;
  932. color: #333;
  933. border-bottom-color: #F1F1F1;
  934. border-bottom-style: solid;
  935. border-bottom-width: 1px;
  936. }
  937. .uni-date-changed--time text {
  938. height: 50px;
  939. line-height: 50px;
  940. }
  941. .uni-date-changed .uni-date-changed--time {
  942. flex: 1;
  943. }
  944. .uni-date-changed--time-date {
  945. color: #333;
  946. opacity: 0.6;
  947. }
  948. .mr-50 {
  949. margin-right: 50px;
  950. }
  951. /* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
  952. .uni-popper__arrow,
  953. .uni-popper__arrow::after {
  954. position: absolute;
  955. display: block;
  956. width: 0;
  957. height: 0;
  958. border: 6px solid transparent;
  959. border-top-width: 0;
  960. }
  961. .uni-popper__arrow {
  962. filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
  963. top: -6px;
  964. left: 10%;
  965. margin-right: 3px;
  966. border-bottom-color: #EBEEF5;
  967. }
  968. .uni-popper__arrow::after {
  969. content: " ";
  970. top: 1px;
  971. margin-left: -6px;
  972. border-bottom-color: #fff;
  973. }
  974. </style>