zxz-uni-datetime-picker.vue 33 KB

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