l-date-time-picker.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <l-picker
  3. :title="title"
  4. :titleStyle="titleStyle"
  5. :confirm-btn="confirmBtn"
  6. :confirm-style="confirmStyle"
  7. :cancel-btn="cancelBtn"
  8. :cancel-style="cancelStyle"
  9. :itemHeight="itemHeight"
  10. :itemColor="itemColor"
  11. :itemFontSize="itemFontSize"
  12. :itemActiveColor="itemActiveColor"
  13. :indicatorStyle="indicatorStyle"
  14. :bgColor="bgColor"
  15. :groupHeight="groupHeight"
  16. :radius="radius"
  17. :value="valueOfPicker"
  18. :columns="columns"
  19. @confirm="onConfirm"
  20. @cancel="onCancel"
  21. @change="onChange"
  22. @pick="onPick">
  23. </l-picker>
  24. </template>
  25. <script lang="ts">
  26. // @ts-nocheck
  27. /**
  28. * DateTimePicker 日期时间选择器
  29. * @description 日期时间选择器组件,支持多种时间模式选择和自定义配置
  30. * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-data-picker
  31. *
  32. * @property {string} cancelBtn 取消按钮文字
  33. * @property {string | object} cancelStyle 取消按钮样式
  34. * @property {string} confirmBtn 确定按钮文字
  35. * @property {string | object} confirmStyle 确定按钮样式
  36. * @property {'zh' | 'tc' | 'en' | 'ja' | 'ko' | 'ru'} customLocale 组件国际化语言
  37. * @property {string|number} end 最大可选时间(默认当前时间+10年)
  38. * @property {string|number} start 最小可选时间(默认当前时间-10年)
  39. * @property {object} steps 时间间隔步数(例:{ minute: 5 })
  40. * @property {string} title 标题文字
  41. * @property {string | object} titleStyle 标题样式
  42. * @property {string|number} value 选中值(支持v-model)
  43. * @property {string|number} defaultValue 默认选中值
  44. * @property {string|number} modelValue 模型值(支持v-model)
  45. * @property {string} format 时间格式(使用day.js格式)
  46. * @property {number} mode 时间选择模式(位运算组合)
  47. * @value 1 年模式
  48. * @value 2 月模式
  49. * @value 4 日模式
  50. * @value 8 时模式
  51. * @value 16 分模式
  52. * @value 32 秒模式
  53. * @example 1 | 2 组合年月选择
  54. * @property {(type: TimeModeValues, columns: DateTimePickerColumn) => DateTimePickerColumn} customFilter 自定义过滤函数
  55. * @property {(type: string, value: string) => string} renderLabel 自定义标签渲染
  56. * @property {boolean} showUnit 是否显示时间单位
  57. * @property {string} itemHeight 选项高度
  58. * @property {string} itemColor 选项文字颜色
  59. * @property {string} itemFontSize 选项字体大小
  60. * @property {string} itemActiveColor 选中项颜色
  61. * @property {string} indicatorStyle 指示器样式
  62. * @property {string} bgColor 背景颜色
  63. * @property {string} groupHeight 选项组高度
  64. * @property {string} radius 圆角半径
  65. * @property {boolean} resetIndex 是否重置选项索引
  66. * @property {number} minHour 最小小时数
  67. * @property {number} maxHour 最大小时数
  68. * @property {number} minMinute 最小分钟数
  69. * @property {number} maxMinute 最大分钟数
  70. *
  71. * @event {Function} confirm 点击确定时触发
  72. * @event {Function} cancel 点击取消时触发
  73. * @event {Function} change 值变化时触发
  74. */
  75. import { defineComponent, computed, ref, watch, onBeforeUnmount} from '@/uni_modules/lime-shared/vue';
  76. import type { DateTimePickerProps, DateValue, DateTimePickerColumn, TimeModeValues, DateTimePickerColumnItem } from './type';
  77. import type { PickerColumn, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from '@/uni_modules/lime-picker';
  78. import { getMeaningColumn } from './utils';
  79. import { DEFAULT_FORMAT, MODE_NAMES, FORMAT_MAP, UNIT_MAP } from './constant';
  80. import { dayuts, Dayuts, DayutsUnit} from '@/uni_modules/lime-dayuts'
  81. import { clamp } from '@/uni_modules/lime-shared/clamp'
  82. import dataTimePickerProps from './props';
  83. export default defineComponent({
  84. name: 'l-date-time-picker',
  85. props: dataTimePickerProps,
  86. emits: ['change', 'cancel', 'confirm', 'pick', 'update:modelValue', 'update:value','input'],
  87. setup(props, {emit}) {
  88. // 默认值
  89. let defaultValue : DateValue = props.value ?? props.defaultValue ?? props.defaultValue ?? Date.now()
  90. const innerValue = computed({
  91. set(value : DateValue) {
  92. if(defaultValue == value) return
  93. defaultValue = value;
  94. emit('change', value)
  95. emit('update:modelValue', value)
  96. emit('update:value', value)
  97. // #ifdef VUE2
  98. emit('input', value)
  99. // #endif
  100. },
  101. get() : DateValue {
  102. const value = props.value ?? props.modelValue ?? defaultValue
  103. return typeof value == 'string' && value.length == 0 ? Date.now() : value
  104. // return props.value ?? props.modelValue ?? defaultValue
  105. }
  106. } as WritableComputedOptions<DateValue>)
  107. const meaningColumn = getMeaningColumn(props.mode);
  108. const isTimeMode = ['hour', 'minute', 'second'].includes(meaningColumn[0]);
  109. const normalize = (val : DateValue | null, defaultDay : Dayuts) : Dayuts => val != null && dayuts(val).isValid() ? dayuts(val) : defaultDay;
  110. const start = computed(() : Dayuts => normalize(props.start as DateValue | null, dayuts().subtract(10, 'year')));
  111. const end = computed(() : Dayuts => normalize(props.end as DateValue | null, dayuts().add(10, 'year')));
  112. const rationalize = (val : Dayuts) : Dayuts => {
  113. if (isTimeMode) return val;
  114. if (val.isBefore(start.value)) return start.value;
  115. if (val.isAfter(end.value)) return end.value;
  116. return val;
  117. };
  118. const calcDate = (currentValue : DateValue | null) : Dayuts => {
  119. if (isTimeMode && (typeof currentValue == 'string')) {
  120. const dateStr = dayuts(start.value).format('YYYY-MM-DD');
  121. currentValue = `${dateStr} ${currentValue}`;
  122. }
  123. return currentValue != null && dayuts(currentValue).isValid() ? rationalize(dayuts(currentValue)) : start.value;
  124. };
  125. const curDate = ref(calcDate(innerValue.value));
  126. const valueOfPicker = computed(() : string[] => meaningColumn.map((item) : string => curDate.value.get(item).toString()));
  127. const columnCache = new Map<string, DateTimePickerColumnItem[]>();
  128. const columns = computed(() : DateTimePickerColumn[] => {
  129. const ret : DateTimePickerColumn[] = [];
  130. const getDate = (date : Dayuts) : number[] => [
  131. date.year(),
  132. date.month() + 1,
  133. date.date(),
  134. date.hour(),
  135. date.minute(),
  136. date.second(),
  137. ];
  138. const [curYear, curMonth, curDay, curHour, curMinute] = getDate(curDate.value);
  139. const [minYear, minMonth, minDay, minHour, minMinute, minSecond] = getDate(start.value);
  140. const [maxYear, maxMonth, maxDay, maxHour, maxMinute, maxSecond] = getDate(end.value);
  141. const isInMinYear = curYear == minYear;
  142. const isInMaxYear = curYear == maxYear;
  143. const isInMinMonth = isInMinYear && curMonth == minMonth;
  144. const isInMaxMonth = isInMaxYear && curMonth === maxMonth;
  145. const isInMinDay = isInMinMonth && curDay == minDay;
  146. const isInMaxDay = isInMaxMonth && curDay == maxDay;
  147. const isInMinHour = isInMinDay && curHour == minHour;
  148. const isInMaxHour = isInMaxDay && curHour == maxHour;
  149. const isInMinMinute = isInMinHour && curMinute == minMinute;
  150. const isInMaxMinute = isInMaxHour && curMinute == maxMinute;
  151. const generateColumn = (type: string, lowerBound: number, upperBound: number) => {
  152. const cacheKey = `${type}-${lowerBound}-${upperBound}`;
  153. if (columnCache.has(cacheKey)) {
  154. ret.push(columnCache.get(cacheKey)!)
  155. return
  156. }
  157. const arr: DateTimePickerColumnItem[] = [];
  158. for (let i = lowerBound; i <= upperBound; i++) {
  159. const value = i;
  160. arr.push({
  161. label: props.renderLabel !=null ? props.renderLabel!(type, i) : `${value}${props.showUnit ? UNIT_MAP.get(type) : ''}`,
  162. value: type == 'month' ? `${value - 1}` : value.toString(),
  163. } as DateTimePickerColumnItem);
  164. }
  165. if(props.customFilter != null) {
  166. const _arr = props.customFilter!(type, arr)
  167. ret.push(_arr)
  168. columnCache.set(cacheKey, _arr);
  169. } else {
  170. ret.push(arr)
  171. columnCache.set(cacheKey, arr);
  172. }
  173. };
  174. if (meaningColumn.includes('year')) {
  175. generateColumn('year', minYear, maxYear);
  176. }
  177. if (meaningColumn.includes('month')) {
  178. const lower = isInMinYear ? minMonth : 1;
  179. const upper = isInMaxYear ? maxMonth : 12;
  180. generateColumn('month', lower, upper);
  181. }
  182. if (meaningColumn.includes('date')) {
  183. const lower = isInMinMonth ? minDay : 1;
  184. const upper = isInMaxMonth ? maxDay : dayuts(`${curYear}-${curMonth}`).daysInMonth();
  185. generateColumn('date', lower, upper);
  186. }
  187. if (meaningColumn.includes('hour')) {
  188. const lower = isInMinDay && !isTimeMode ? minHour : clamp(props.minHour, 0, 23);// 0;
  189. const upper = isInMaxDay && !isTimeMode ? maxHour : clamp(props.maxHour, lower, 23);//23;
  190. generateColumn('hour', lower, upper);
  191. }
  192. if (meaningColumn.includes('minute')) {
  193. const lower = isInMinHour && !isTimeMode ? minMinute : clamp(props.minMinute, 0, 59);//0;
  194. const upper = isInMaxHour && !isTimeMode ? maxMinute : clamp(props.maxMinute, lower, 59);//59;
  195. generateColumn('minute', lower, upper);
  196. }
  197. if (meaningColumn.includes('second')) {
  198. const lower = isInMinMinute && !isTimeMode ? minSecond : 0;
  199. const upper = isInMaxMinute && !isTimeMode ? maxSecond : 59;
  200. generateColumn('second', lower, upper);
  201. }
  202. return ret;
  203. })
  204. const innterFormat = computed(() : string => {
  205. const first = meaningColumn.length > 0 ? meaningColumn[0] : 'year';
  206. const last = meaningColumn.length > 0 ? meaningColumn[meaningColumn.length - 1] : 'date';
  207. const format = DEFAULT_FORMAT.substring(
  208. DEFAULT_FORMAT.indexOf(FORMAT_MAP.get(first)!),
  209. DEFAULT_FORMAT.lastIndexOf(FORMAT_MAP.get(last)!) + FORMAT_MAP.get(last)!.length
  210. )
  211. return format
  212. })
  213. const onConfirm = ({ values } : PickerConfirmEvent) => {
  214. // const first = meaningColumn.length > 0 ? meaningColumn[0]: 'year';
  215. // const last = meaningColumn.length > 0 ? meaningColumn[meaningColumn.length - 1]: 'date';
  216. // const format = DEFAULT_FORMAT.substring(
  217. // DEFAULT_FORMAT.indexOf(FORMAT_MAP.get(first)!),
  218. // DEFAULT_FORMAT.lastIndexOf(FORMAT_MAP.get(last)!) + FORMAT_MAP.get(last)!.length
  219. // )
  220. let cur = curDate.value
  221. // MODE_NAMES
  222. values.forEach((item, index) => {
  223. const type = meaningColumn[index]
  224. cur = cur.set(type, parseInt(`${item}`, 10))
  225. })
  226. const curValue = cur.format(props.format)
  227. innerValue.value = cur.format(innterFormat.value);
  228. emit('confirm', curValue);
  229. }
  230. const onCancel = () => {
  231. emit('cancel')
  232. }
  233. const onPick = ({ values, column, index } : PickerPickEvent) => {
  234. const type = meaningColumn[column];
  235. const val = curDate.value.set(type as DayutsUnit, parseInt(columns.value[column][index].value, 10));
  236. curDate.value = rationalize(val);
  237. emit('pick', rationalize(val).format(props.format))
  238. }
  239. const onChange = (values: PickerValue[]) => {
  240. let cur = curDate.value
  241. values.forEach((item, index) => {
  242. const type = meaningColumn[index]
  243. cur = cur.set(type, parseInt(`${item}`, 10))
  244. })
  245. curDate.value = rationalize(cur as Dayuts)
  246. const curValue = curDate.value.format(innterFormat.value)
  247. innerValue.value = curValue
  248. }
  249. const stop = watch(innerValue, (val : DateValue) => {
  250. curDate.value = calcDate(val);
  251. });
  252. onBeforeUnmount(()=>{
  253. stop()
  254. })
  255. return {
  256. valueOfPicker,
  257. columns,
  258. onConfirm,
  259. onCancel,
  260. onChange,
  261. onPick
  262. }
  263. }
  264. })
  265. </script>
  266. <style>
  267. </style>