wd-datetime-picker.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <template>
  2. <view :class="`wd-datetime-picker ${customClass}`" :style="customStyle">
  3. <wd-cell
  4. v-if="!$slots.default"
  5. :title="label"
  6. :required="required"
  7. :size="size"
  8. :title-width="labelWidth"
  9. :prop="prop"
  10. :rules="rules"
  11. :clickable="!disabled && !readonly"
  12. :value-align="alignRight ? 'right' : 'left'"
  13. :custom-class="cellClass"
  14. :custom-style="customStyle"
  15. :custom-title-class="customLabelClass"
  16. :custom-value-class="customValueClass"
  17. :ellipsis="ellipsis"
  18. :use-title-slot="!!$slots.label"
  19. :marker-side="markerSide"
  20. @click="showPopup"
  21. >
  22. <template v-if="$slots.label" #title>
  23. <slot name="label"></slot>
  24. </template>
  25. <template #default>
  26. <template v-if="region">
  27. <view v-if="isArray(showValue)">
  28. <text :class="showValue[0] ? '' : 'wd-datetime-picker__placeholder'">
  29. {{ showValue[0] ? showValue[0] : placeholder || translate('placeholder') }}
  30. </text>
  31. {{ translate('to') }}
  32. <text :class="showValue[1] ? '' : 'wd-datetime-picker__placeholder'">
  33. {{ showValue[1] ? showValue[1] : placeholder || translate('placeholder') }}
  34. </text>
  35. </view>
  36. <view v-else class="wd-datetime-picker__cell-placeholder">
  37. {{ placeholder || translate('placeholder') }}
  38. </view>
  39. </template>
  40. <view v-else :class="showValue ? '' : 'wd-datetime-picker__placeholder'">
  41. {{ showValue ? showValue : placeholder || translate('placeholder') }}
  42. </view>
  43. </template>
  44. <template #right-icon>
  45. <wd-icon v-if="showArrow" custom-class="wd-datetime-picker__arrow" name="arrow-right" />
  46. <view v-else-if="showClear" @click.stop="handleClear">
  47. <wd-icon custom-class="wd-datetime-picker__clear" name="error-fill" />
  48. </view>
  49. </template>
  50. </wd-cell>
  51. <view v-else @click="showPopup">
  52. <slot></slot>
  53. </view>
  54. <!--弹出层,picker-view 在隐藏时修改值,会触发多次change事件,从而导致所有列选中第一项,因此picker在关闭时不隐藏 -->
  55. <wd-popup
  56. v-model="popupShow"
  57. position="bottom"
  58. :hide-when-close="false"
  59. :close-on-click-modal="closeOnClickModal"
  60. :safe-area-inset-bottom="safeAreaInsetBottom"
  61. :z-index="zIndex"
  62. :root-portal="rootPortal"
  63. @close="onCancel"
  64. custom-class="wd-datetime-picker__popup"
  65. >
  66. <view class="wd-datetime-picker__wraper">
  67. <!--toolBar-->
  68. <view class="wd-datetime-picker__toolbar" @touchmove="noop">
  69. <!--取消按钮-->
  70. <view class="wd-datetime-picker__action wd-datetime-picker__action--cancel" @click="onCancel">
  71. {{ cancelButtonText || translate('cancel') }}
  72. </view>
  73. <!--标题-->
  74. <view v-if="title" class="wd-datetime-picker__title">{{ title }}</view>
  75. <!--确定按钮-->
  76. <view :class="`wd-datetime-picker__action ${loading || isLoading ? 'is-loading' : ''}`" @click="onConfirm">
  77. {{ confirmButtonText || translate('confirm') }}
  78. </view>
  79. </view>
  80. <!-- 区域选择tab展示 -->
  81. <view v-if="region" class="wd-datetime-picker__region-tabs">
  82. <view :class="`wd-datetime-picker__region ${showStart ? 'is-active' : ''} `" @click="tabChange">
  83. <view>{{ translate('start') }}</view>
  84. <view class="wd-datetime-picker__region-time">{{ showTabLabel[0] }}</view>
  85. </view>
  86. <view :class="`wd-datetime-picker__region ${showStart ? '' : 'is-active'}`" @click="tabChange">
  87. <view>{{ translate('end') }}</view>
  88. <view class="wd-datetime-picker__region-time">{{ showTabLabel[1] }}</view>
  89. </view>
  90. </view>
  91. <!--datetimePickerView-->
  92. <view :class="showStart ? 'wd-datetime-picker__show' : 'wd-datetime-picker__hidden'">
  93. <wd-datetime-picker-view
  94. :custom-class="customViewClass"
  95. ref="datetimePickerView"
  96. :type="type"
  97. v-model="innerValue"
  98. :loading="loading || isLoading"
  99. :loading-color="loadingColor"
  100. :columns-height="columnsHeight"
  101. :value-key="valueKey"
  102. :label-key="labelKey"
  103. :formatter="formatter"
  104. :filter="filter"
  105. :column-formatter="isArray(modelValue) ? startColumnFormatter : undefined"
  106. :max-hour="maxHour"
  107. :min-hour="minHour"
  108. :max-date="maxDate"
  109. :min-date="minDate"
  110. :max-minute="maxMinute"
  111. :min-minute="minMinute"
  112. :use-second="useSecond"
  113. :min-second="minSecond"
  114. :max-second="maxSecond"
  115. :immediate-change="immediateChange"
  116. @change="onChangeStart"
  117. @pickstart="onPickStart"
  118. @pickend="onPickEnd"
  119. />
  120. </view>
  121. <view :class="showStart ? 'wd-datetime-picker__hidden' : 'wd-datetime-picker__show'">
  122. <wd-datetime-picker-view
  123. :custom-class="customViewClass"
  124. ref="datetimePickerView1"
  125. :type="type"
  126. v-model="endInnerValue"
  127. :loading="loading || isLoading"
  128. :loading-color="loadingColor"
  129. :columns-height="columnsHeight"
  130. :value-key="valueKey"
  131. :label-key="labelKey"
  132. :formatter="formatter"
  133. :filter="filter"
  134. :column-formatter="isArray(modelValue) ? endColumnFormatter : undefined"
  135. :max-hour="maxHour"
  136. :min-hour="minHour"
  137. :max-date="maxDate"
  138. :min-date="minDate"
  139. :max-minute="maxMinute"
  140. :min-minute="minMinute"
  141. :use-second="useSecond"
  142. :min-second="minSecond"
  143. :max-second="maxSecond"
  144. :immediate-change="immediateChange"
  145. @change="onChangeEnd"
  146. @pickstart="onPickStart"
  147. @pickend="onPickEnd"
  148. />
  149. </view>
  150. </view>
  151. </wd-popup>
  152. </view>
  153. </template>
  154. <script lang="ts">
  155. export default {
  156. name: 'wd-datetime-picker',
  157. options: {
  158. virtualHost: true,
  159. addGlobalClass: true,
  160. styleIsolation: 'shared'
  161. }
  162. }
  163. </script>
  164. <script lang="ts" setup>
  165. import wdPopup from '../wd-popup/wd-popup.vue'
  166. import wdDatetimePickerView from '../wd-datetime-picker-view/wd-datetime-picker-view.vue'
  167. import wdCell from '../wd-cell/wd-cell.vue'
  168. import wdIcon from '../wd-icon/wd-icon.vue'
  169. import { computed, getCurrentInstance, nextTick, onBeforeMount, onMounted, ref, watch } from 'vue'
  170. import { deepClone, isArray, isDef, isEqual, isFunction, padZero } from '../common/util'
  171. import { type DatetimePickerViewInstance, type DatetimePickerViewColumnType, type DatetimePickerViewExpose } from '../wd-datetime-picker-view/types'
  172. import { useTranslate } from '../composables/useTranslate'
  173. import { datetimePickerProps, type DatetimePickerExpose } from './types'
  174. import dayjs from '../../dayjs'
  175. import { getPickerValue } from '../wd-datetime-picker-view/util'
  176. const props = defineProps(datetimePickerProps)
  177. const emit = defineEmits(['change', 'open', 'toggle', 'cancel', 'confirm', 'clear', 'update:modelValue'])
  178. const { translate } = useTranslate('datetime-picker')
  179. const datetimePickerView = ref<DatetimePickerViewInstance>()
  180. const datetimePickerView1 = ref<DatetimePickerViewInstance>()
  181. const showValue = ref<string | Date | Array<string | Date>>('')
  182. const popupShow = ref<boolean>(false)
  183. const showStart = ref<boolean>(true)
  184. const region = ref<boolean>(false)
  185. const showTabLabel = ref<string[]>([])
  186. const innerValue = ref<string | number>('')
  187. const endInnerValue = ref<string | number>('')
  188. const isPicking = ref<boolean>(false) // 判断pickview是否还在滑动中
  189. const hasConfirmed = ref<boolean>(false) // 判断用户是否点击了确认按钮
  190. const isLoading = ref<boolean>(false) // 加载
  191. const { proxy } = getCurrentInstance() as any
  192. const cellClass = computed(() => {
  193. const classes = ['wd-datetime-picker__cell']
  194. if (props.disabled) classes.push('is-disabled')
  195. if (props.readonly) classes.push('is-readonly')
  196. if (props.error) classes.push('is-error')
  197. return classes.join(' ')
  198. })
  199. watch(
  200. () => props.modelValue,
  201. (val, oldVal) => {
  202. if (isEqual(val, oldVal)) return
  203. if (isArray(val)) {
  204. region.value = true
  205. innerValue.value = deepClone(getDefaultInnerValue(true))
  206. endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
  207. } else {
  208. // 每次value更新时都需要刷新整个列表
  209. innerValue.value = deepClone(getDefaultInnerValue())
  210. }
  211. nextTick(() => {
  212. setShowValue(false, false, true)
  213. })
  214. },
  215. {
  216. deep: true,
  217. immediate: true
  218. }
  219. )
  220. watch(
  221. () => props.displayFormat,
  222. (fn) => {
  223. if (fn && !isFunction(fn)) {
  224. console.error('The type of displayFormat must be Function')
  225. }
  226. },
  227. {
  228. deep: true,
  229. immediate: true
  230. }
  231. )
  232. watch(
  233. () => props.filter,
  234. (fn) => {
  235. if (fn && !isFunction(fn)) {
  236. console.error('The type of filter must be Function')
  237. }
  238. },
  239. {
  240. deep: true,
  241. immediate: true
  242. }
  243. )
  244. watch(
  245. () => props.formatter,
  246. (fn) => {
  247. if (fn && !isFunction(fn)) {
  248. console.error('The type of formatter must be Function')
  249. }
  250. },
  251. {
  252. deep: true,
  253. immediate: true
  254. }
  255. )
  256. watch(
  257. () => props.beforeConfirm,
  258. (fn) => {
  259. if (fn && !isFunction(fn)) {
  260. console.error('The type of beforeConfirm must be Function')
  261. }
  262. },
  263. {
  264. deep: true,
  265. immediate: true
  266. }
  267. )
  268. watch(
  269. () => props.displayFormatTabLabel,
  270. (fn) => {
  271. if (fn && !isFunction(fn)) {
  272. console.error('The type of displayFormatTabLabel must be Function')
  273. }
  274. },
  275. {
  276. deep: true,
  277. immediate: true
  278. }
  279. )
  280. watch(
  281. () => props.defaultValue,
  282. (val) => {
  283. if (isArray(val) || region.value) {
  284. innerValue.value = deepClone(getDefaultInnerValue(true))
  285. endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
  286. } else {
  287. innerValue.value = deepClone(getDefaultInnerValue())
  288. }
  289. },
  290. {
  291. deep: true,
  292. immediate: true
  293. }
  294. )
  295. // 是否展示清除按钮
  296. const showClear = computed(() => {
  297. return (
  298. props.clearable &&
  299. !props.disabled &&
  300. !props.readonly &&
  301. ((!isArray(showValue.value) && showValue.value) || (isArray(showValue.value) && (showValue.value[0] || showValue.value[1])))
  302. )
  303. })
  304. // 是否展示箭头
  305. const showArrow = computed(() => {
  306. return !props.disabled && !props.readonly && !showClear.value
  307. })
  308. /**
  309. * @description 处理时间边界值判断
  310. * @param isStart 是否是开始时间
  311. * @param columnType 当前列类型
  312. * @param value 当前值
  313. * @param currentArray 当前完整选择的数组
  314. * @param boundary 边界值数组
  315. * @returns 是否超出边界
  316. */
  317. function handleBoundaryValue(
  318. isStart: boolean,
  319. columnType: DatetimePickerViewColumnType,
  320. value: number,
  321. currentArray: number[],
  322. boundary: number[]
  323. ): boolean {
  324. const { type, useSecond } = props
  325. switch (type) {
  326. case 'datetime': {
  327. const [year, month, date, hour, minute, second] = boundary
  328. if (columnType === 'year') {
  329. return isStart ? value > year : value < year
  330. }
  331. if (columnType === 'month' && currentArray[0] === year) {
  332. return isStart ? value > month : value < month
  333. }
  334. if (columnType === 'date' && currentArray[0] === year && currentArray[1] === month) {
  335. return isStart ? value > date : value < date
  336. }
  337. if (columnType === 'hour' && currentArray[0] === year && currentArray[1] === month && currentArray[2] === date) {
  338. return isStart ? value > hour : value < hour
  339. }
  340. if (columnType === 'minute' && currentArray[0] === year && currentArray[1] === month && currentArray[2] === date && currentArray[3] === hour) {
  341. return isStart ? value > minute : value < minute
  342. }
  343. if (
  344. useSecond &&
  345. columnType === 'second' &&
  346. currentArray[0] === year &&
  347. currentArray[1] === month &&
  348. currentArray[2] === date &&
  349. currentArray[3] === hour &&
  350. currentArray[4] === minute
  351. ) {
  352. return isStart ? value > second : value < second
  353. }
  354. break
  355. }
  356. case 'year-month': {
  357. const [year, month] = boundary
  358. if (columnType === 'year') {
  359. return isStart ? value > year : value < year
  360. }
  361. if (columnType === 'month' && currentArray[0] === year) {
  362. return isStart ? value > month : value < month
  363. }
  364. break
  365. }
  366. case 'year': {
  367. const [year] = boundary
  368. if (columnType === 'year') {
  369. return isStart ? value > year : value < year
  370. }
  371. break
  372. }
  373. case 'date': {
  374. const [year, month, date] = boundary
  375. if (columnType === 'year') {
  376. return isStart ? value > year : value < year
  377. }
  378. if (columnType === 'month' && currentArray[0] === year) {
  379. return isStart ? value > month : value < month
  380. }
  381. if (columnType === 'date' && currentArray[0] === year && currentArray[1] === month) {
  382. return isStart ? value > date : value < date
  383. }
  384. break
  385. }
  386. case 'time': {
  387. const [hour, minute, second] = boundary
  388. if (columnType === 'hour') {
  389. return isStart ? value > hour : value < hour
  390. }
  391. if (columnType === 'minute' && currentArray[0] === hour) {
  392. return isStart ? value > minute : value < minute
  393. }
  394. if (useSecond && columnType === 'second' && currentArray[0] === hour && currentArray[1] === minute) {
  395. return isStart ? value > second : value < second
  396. }
  397. break
  398. }
  399. }
  400. return false
  401. }
  402. function startColumnFormatter(picker: DatetimePickerViewExpose) {
  403. return customColumnFormatter(picker, 'start')
  404. }
  405. function endColumnFormatter(picker: DatetimePickerViewExpose) {
  406. return customColumnFormatter(picker, 'end')
  407. }
  408. /**
  409. * @description 自定义列项筛选规则
  410. */
  411. const customColumnFormatter = (picker: DatetimePickerViewExpose, pickerType: 'start' | 'end') => {
  412. if (!picker) return []
  413. const { type } = props
  414. const startSymbol = pickerType === 'start'
  415. const { formatter } = props
  416. const start = picker.correctValue(innerValue.value)
  417. const end = picker.correctValue(endInnerValue.value)
  418. const currentValue = startSymbol ? getPickerValue(start, type, props.useSecond) : getPickerValue(end, type, props.useSecond)
  419. const boundary = startSymbol ? getPickerValue(end, type, props.useSecond) : getPickerValue(start, type, props.useSecond)
  420. const columns = picker.getOriginColumns()
  421. return columns.map((column, _) => {
  422. return column.values.map((value) => {
  423. const disabled = handleBoundaryValue(startSymbol, column.type, value, currentValue, boundary)
  424. return {
  425. label: formatter ? formatter(column.type, padZero(value)) : padZero(value),
  426. value,
  427. disabled
  428. }
  429. })
  430. })
  431. }
  432. onBeforeMount(() => {
  433. const { modelValue: value } = props
  434. if (isArray(value)) {
  435. region.value = true
  436. innerValue.value = deepClone(getDefaultInnerValue(true))
  437. endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
  438. } else {
  439. innerValue.value = deepClone(getDefaultInnerValue())
  440. }
  441. })
  442. onMounted(() => {
  443. setShowValue(false, false, true)
  444. })
  445. /**
  446. * @description 根据传入的picker,picker组件获取当前cell展示值。
  447. */
  448. function getSelects(picker: 'before' | 'after') {
  449. let value = picker === 'before' ? innerValue.value : endInnerValue.value
  450. let selected: number[] = []
  451. if (value) {
  452. selected = getPickerValue(value, props.type, props.useSecond)
  453. }
  454. let selects = selected.map((value) => {
  455. return {
  456. [props.labelKey]: padZero(value),
  457. [props.valueKey]: value
  458. }
  459. })
  460. return selects
  461. }
  462. function noop() {}
  463. function getDefaultInnerValue(isRegion?: boolean, isEnd?: boolean): string | number {
  464. const { modelValue: value, defaultValue, maxDate, minDate, type } = props
  465. if (isRegion) {
  466. const index = isEnd ? 1 : 0
  467. const targetValue = isArray(value) ? (value[index] as string) : ''
  468. const targetDefault = isArray(defaultValue) ? (defaultValue[index] as string) : ''
  469. const maxValue = type === 'time' ? dayjs(maxDate).format('HH:mm') : maxDate
  470. const minValue = type === 'time' ? dayjs(minDate).format('HH:mm') : minDate
  471. return targetValue || targetDefault || (isEnd ? maxValue : minValue)
  472. } else {
  473. return isDef(value || defaultValue) ? (value as string) || (defaultValue as string) : ''
  474. }
  475. }
  476. // 对外暴露接口,打开弹框
  477. function open() {
  478. showPopup()
  479. }
  480. // 对外暴露接口,关闭弹框
  481. function close() {
  482. onCancel()
  483. }
  484. function showPopup() {
  485. if (props.disabled || props.readonly) return
  486. emit('open')
  487. if (region.value) {
  488. popupShow.value = true
  489. showStart.value = true
  490. innerValue.value = deepClone(getDefaultInnerValue(true, false))
  491. endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
  492. } else {
  493. popupShow.value = true
  494. innerValue.value = deepClone(getDefaultInnerValue())
  495. }
  496. setShowValue(true, false, true)
  497. }
  498. /**
  499. * @description 区域选择时tab标签切换时触发
  500. */
  501. function tabChange() {
  502. showStart.value = !showStart.value
  503. // 列项刷新多级联动挂载到datetimepickerView
  504. const picker = showStart.value ? datetimePickerView.value : datetimePickerView1.value
  505. picker!.setColumns(picker!.updateColumns())
  506. emit('toggle', showStart.value ? innerValue.value : endInnerValue.value)
  507. }
  508. /**
  509. * @description datetimePickerView change 事件
  510. */
  511. function onChangeStart({ value }: { value: number | string }) {
  512. if (!datetimePickerView.value) return
  513. if (region.value && !datetimePickerView1.value) return
  514. if (region.value) {
  515. // 区间选择才需要处理边界值
  516. // 区间选择才需要处理边界值
  517. const currentArray = getPickerValue(value, props.type, props.useSecond)
  518. const boundaryArray = getPickerValue(endInnerValue.value, props.type, props.useSecond)
  519. const columns = datetimePickerView.value.getOriginColumns()
  520. // 检查是否有任何列超出边界
  521. const needsAdjust = columns.some((column, index) => {
  522. return handleBoundaryValue(true, column.type, currentArray[index], currentArray, boundaryArray)
  523. })
  524. innerValue.value = deepClone(needsAdjust ? endInnerValue.value : value)
  525. nextTick(() => {
  526. showTabLabel.value = [setTabLabel(), deepClone(showTabLabel.value[1])]
  527. emit('change', {
  528. value: [innerValue.value, endInnerValue.value]
  529. })
  530. // 更新两个picker的列
  531. datetimePickerView.value && datetimePickerView.value.setColumns(datetimePickerView.value.updateColumns())
  532. datetimePickerView1.value && datetimePickerView1.value.setColumns(datetimePickerView1.value.updateColumns())
  533. })
  534. } else {
  535. // 非区间选择直接设置值即可
  536. innerValue.value = deepClone(value)
  537. emit('change', {
  538. value: innerValue.value
  539. })
  540. }
  541. }
  542. /**
  543. * @description 区域选择 下方 datetimePickerView change 事件
  544. */
  545. function onChangeEnd({ value }: { value: number | string }) {
  546. if (!datetimePickerView.value || !datetimePickerView1.value) return
  547. const currentArray = getPickerValue(value, props.type)
  548. const boundaryArray = getPickerValue(innerValue.value, props.type)
  549. const columns = datetimePickerView1.value.getOriginColumns()
  550. // 检查是否有任何列超出边界
  551. const needsAdjust = columns.some((column, index) => {
  552. return handleBoundaryValue(false, column.type, currentArray[index], currentArray, boundaryArray)
  553. })
  554. endInnerValue.value = deepClone(needsAdjust ? innerValue.value : value)
  555. nextTick(() => {
  556. showTabLabel.value = [deepClone(showTabLabel.value[0]), setTabLabel(1)]
  557. emit('change', {
  558. value: [innerValue.value, endInnerValue.value]
  559. })
  560. // 更新两个picker的列
  561. datetimePickerView.value && datetimePickerView.value.setColumns(datetimePickerView.value.updateColumns())
  562. datetimePickerView1.value && datetimePickerView1.value.setColumns(datetimePickerView1.value.updateColumns())
  563. })
  564. }
  565. /**
  566. * @description 点击取消按钮触发。关闭popup,触发cancel事件。
  567. */
  568. function onCancel() {
  569. popupShow.value = false
  570. setTimeout(() => {
  571. if (region.value) {
  572. innerValue.value = deepClone(getDefaultInnerValue(true))
  573. endInnerValue.value = deepClone(getDefaultInnerValue(true, true))
  574. } else {
  575. innerValue.value = deepClone(getDefaultInnerValue())
  576. }
  577. }, 200)
  578. emit('cancel')
  579. }
  580. /** picker触发confirm事件,同步触发confirm事件 */
  581. function onConfirm() {
  582. if (props.loading || isLoading.value) return
  583. // 如果当前还在滑动且未停止下来,则锁住先不确认,等滑完再自动确认,避免pickview值未更新
  584. if (isPicking.value) {
  585. hasConfirmed.value = true
  586. return
  587. }
  588. const { beforeConfirm } = props
  589. if (beforeConfirm) {
  590. beforeConfirm(
  591. region.value ? [innerValue.value, endInnerValue.value] : innerValue.value,
  592. (isPass: boolean) => {
  593. isPass && handleConfirm()
  594. },
  595. proxy.$.exposed
  596. )
  597. } else {
  598. handleConfirm()
  599. }
  600. }
  601. function onPickStart() {
  602. isPicking.value = true
  603. }
  604. function onPickEnd() {
  605. isPicking.value = false
  606. // 延迟一会,因为组件层级嵌套过多,日期的计算时间也较长
  607. setTimeout(() => {
  608. if (hasConfirmed.value) {
  609. hasConfirmed.value = false
  610. onConfirm()
  611. }
  612. }, 50)
  613. }
  614. function handleConfirm() {
  615. if (props.loading || isLoading.value || props.disabled) {
  616. popupShow.value = false
  617. return
  618. }
  619. const value = region.value ? [innerValue.value, endInnerValue.value] : innerValue.value
  620. popupShow.value = false
  621. emit('update:modelValue', value)
  622. emit('confirm', {
  623. value
  624. })
  625. setShowValue(false, true)
  626. }
  627. /**
  628. * @description 设置区域选择 tab 标签展示值
  629. * @param {Number} index 索引标志位,有三个有效值; 0(默认):上方picker索引; 1:下方picker索引;
  630. * @return {String} showTabLabel
  631. */
  632. function setTabLabel(index: number = 0) {
  633. if (region.value) {
  634. let items: Record<string, any>[] = []
  635. if (index === 0) {
  636. items = ((datetimePickerView.value ? datetimePickerView.value!.getSelects() : undefined) ||
  637. (innerValue.value && getSelects('before'))) as Record<string, any>[]
  638. } else {
  639. items = ((datetimePickerView1.value ? datetimePickerView1.value!.getSelects() : undefined) ||
  640. (endInnerValue.value && getSelects('after'))) as Record<string, any>[]
  641. }
  642. return defaultDisplayFormat(items, true)
  643. } else {
  644. return ''
  645. }
  646. }
  647. /**
  648. * @description 设置展示值
  649. * @param {Boolean} tab 是否修改tab展示值(尽在区域选择情况下生效)
  650. * @param {Boolean} isConfirm 是否提交当前修改
  651. */
  652. function setShowValue(tab: boolean = false, isConfirm: boolean = false, beforeMount: boolean = false) {
  653. if (region.value) {
  654. const items = beforeMount
  655. ? (innerValue.value && getSelects('before')) || []
  656. : (datetimePickerView.value && datetimePickerView.value.getSelects && datetimePickerView.value.getSelects()) || []
  657. const endItems = beforeMount
  658. ? (endInnerValue.value && getSelects('after')) || []
  659. : (datetimePickerView1.value && datetimePickerView1.value.getSelects && datetimePickerView1.value.getSelects()) || []
  660. showValue.value = tab
  661. ? showValue.value
  662. : [
  663. (props.modelValue as (string | number)[])[0] || isConfirm ? defaultDisplayFormat(items as Record<string, any>[]) : '',
  664. (props.modelValue as (string | number)[])[1] || isConfirm ? defaultDisplayFormat(endItems as Record<string, any>[]) : ''
  665. ]
  666. showTabLabel.value = [defaultDisplayFormat(items as Record<string, any>[], true), defaultDisplayFormat(endItems as Record<string, any>[], true)]
  667. } else {
  668. const items = beforeMount
  669. ? (innerValue.value && getSelects('before')) || []
  670. : (datetimePickerView.value && datetimePickerView.value.getSelects && datetimePickerView.value.getSelects()) || []
  671. showValue.value = deepClone(props.modelValue || isConfirm ? defaultDisplayFormat(items as Record<string, any>[]) : '')
  672. }
  673. }
  674. /**
  675. * @description 设置展示值
  676. * @param {Object} items 获取到的选中项 包含 { value, label }
  677. * @param {Boolean} tabLabel 当前返回的是否是展示tab上的标签
  678. * @return {String} showValue / showTabLabel
  679. */
  680. function defaultDisplayFormat(items: Record<string, any>[], tabLabel: boolean = false) {
  681. if (items.length === 0) return ''
  682. if (tabLabel && props.displayFormatTabLabel) {
  683. return props.displayFormatTabLabel(items)
  684. }
  685. if (props.displayFormat) {
  686. return props.displayFormat(items)
  687. }
  688. // 如果使用了自定义的的formatter,defaultDisplayFormat无效
  689. if (props.formatter) {
  690. const typeMaps = {
  691. year: ['year'],
  692. datetime: props.useSecond ? ['year', 'month', 'date', 'hour', 'minute', 'second'] : ['year', 'month', 'date', 'hour', 'minute'],
  693. date: ['year', 'month', 'date'],
  694. time: props.useSecond ? ['hour', 'minute', 'second'] : ['hour', 'minute'],
  695. 'year-month': ['year', 'month']
  696. }
  697. return items
  698. .map((item, index) => {
  699. return props.formatter!(typeMaps[props.type][index], item.value)
  700. })
  701. .join('')
  702. }
  703. switch (props.type) {
  704. case 'year':
  705. return items[0].label
  706. case 'date':
  707. return `${items[0].label}-${items[1].label}-${items[2].label}`
  708. case 'year-month':
  709. return `${items[0].label}-${items[1].label}`
  710. case 'time':
  711. return props.useSecond ? `${items[0].label}:${items[1].label}:${items[2].label}` : `${items[0].label}:${items[1].label}`
  712. case 'datetime':
  713. return props.useSecond
  714. ? `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}:${items[5].label}`
  715. : `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}`
  716. }
  717. }
  718. function setLoading(loading: boolean) {
  719. isLoading.value = loading
  720. }
  721. function handleClear() {
  722. emit('clear')
  723. emit('update:modelValue', '')
  724. setShowValue(false, true)
  725. }
  726. defineExpose<DatetimePickerExpose>({
  727. open,
  728. close,
  729. setLoading
  730. })
  731. </script>
  732. <style lang="scss" scoped>
  733. @import './index.scss';
  734. </style>