uni-datetime-picker.vue 28 KB

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