cl-select-date.uvue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. <template>
  2. <cl-select-trigger
  3. v-if="showTrigger"
  4. :pt="{
  5. className: pt.trigger?.className,
  6. icon: pt.trigger?.icon
  7. }"
  8. :placeholder="placeholder"
  9. :disabled="disabled"
  10. :focus="popupRef?.isOpen"
  11. :text="text"
  12. arrow-icon="calendar-line"
  13. @open="open()"
  14. @clear="clear"
  15. ></cl-select-trigger>
  16. <cl-popup
  17. ref="popupRef"
  18. v-model="visible"
  19. :title="title"
  20. :pt="{
  21. className: pt.popup?.className,
  22. header: pt.popup?.header,
  23. container: pt.popup?.container,
  24. mask: pt.popup?.mask,
  25. draw: pt.popup?.draw
  26. }"
  27. @closed="onClosed"
  28. >
  29. <view class="cl-select-popup" @touchmove.stop>
  30. <view class="cl-select-popup__range" v-if="rangeable">
  31. <view class="cl-select-popup__range-shortcuts" v-if="showShortcuts">
  32. <cl-tag
  33. v-for="(item, index) in shortcuts"
  34. :key="index"
  35. plain
  36. :type="shortcutsIndex == index ? 'primary' : 'info'"
  37. @tap="setRangeValue(item.value, index)"
  38. >
  39. {{ item.label }}
  40. </cl-tag>
  41. </view>
  42. <view class="cl-select-popup__range-values">
  43. <view
  44. class="cl-select-popup__range-values-start"
  45. :class="{
  46. 'is-dark': isDark,
  47. active: rangeIndex == 0
  48. }"
  49. @tap="setRange(0)"
  50. >
  51. <cl-text
  52. v-if="values.length > 0 && values[0] != ''"
  53. :pt="{
  54. className: 'text-center'
  55. }"
  56. >{{ values[0] }}</cl-text
  57. >
  58. <cl-text
  59. v-else
  60. :pt="{
  61. className: 'text-center text-surface-400'
  62. }"
  63. >{{ startPlaceholder }}</cl-text
  64. >
  65. </view>
  66. <cl-text :pt="{ className: 'mx-3' }">{{ rangeSeparator }}</cl-text>
  67. <view
  68. class="cl-select-popup__range-values-end"
  69. :class="{
  70. 'is-dark': isDark,
  71. active: rangeIndex == 1
  72. }"
  73. @tap="setRange(1)"
  74. >
  75. <cl-text
  76. v-if="values.length > 1 && values[1] != ''"
  77. :pt="{
  78. className: 'text-center'
  79. }"
  80. >{{ values[1] }}</cl-text
  81. >
  82. <cl-text
  83. v-else
  84. :pt="{
  85. className: 'text-center text-surface-400'
  86. }"
  87. >{{ endPlaceholder }}</cl-text
  88. >
  89. </view>
  90. </view>
  91. </view>
  92. <view class="cl-select-popup__picker">
  93. <cl-picker-view
  94. :headers="headers"
  95. :value="indexes"
  96. :columns="columns"
  97. :reset-on-change="false"
  98. @change-value="onChange"
  99. ></cl-picker-view>
  100. </view>
  101. <view class="cl-select-popup__op">
  102. <cl-button
  103. v-if="showCancel"
  104. size="large"
  105. text
  106. border
  107. type="light"
  108. :pt="{
  109. className: 'flex-1 !rounded-xl h-[80rpx]'
  110. }"
  111. @tap="close"
  112. >{{ cancelText }}</cl-button
  113. >
  114. <cl-button
  115. v-if="showConfirm"
  116. size="large"
  117. :pt="{
  118. className: 'flex-1 !rounded-xl h-[80rpx]'
  119. }"
  120. @tap="confirm"
  121. >{{ confirmText }}</cl-button
  122. >
  123. </view>
  124. </view>
  125. </cl-popup>
  126. </template>
  127. <script setup lang="ts">
  128. import { ref, computed, type PropType, watch, nextTick } from "vue";
  129. import type { ClSelectDateShortcut, ClSelectOption } from "../../types";
  130. import { dayUts, isDark, isEmpty, isNull, parsePt } from "@/cool";
  131. import type { ClSelectTriggerPassThrough } from "../cl-select-trigger/props";
  132. import type { ClPopupPassThrough } from "../cl-popup/props";
  133. import { t } from "@/locale";
  134. import { useUi } from "../../hooks";
  135. import { config } from "../../config";
  136. defineOptions({
  137. name: "cl-select-date"
  138. });
  139. // 组件属性定义
  140. const props = defineProps({
  141. // 透传样式配置,支持外部自定义样式
  142. pt: {
  143. type: Object,
  144. default: () => ({})
  145. },
  146. // 选择器的值,外部v-model绑定
  147. modelValue: {
  148. type: String,
  149. default: ""
  150. },
  151. // 选择器的范围值,外部v-model:values绑定
  152. values: {
  153. type: Array as PropType<string[]>,
  154. default: () => []
  155. },
  156. // 表头
  157. headers: {
  158. type: Array as PropType<string[]>,
  159. default: () => [t("年"), t("月"), t("日"), t("时"), t("分"), t("秒")]
  160. },
  161. // 选择器标题
  162. title: {
  163. type: String,
  164. default: () => t("请选择")
  165. },
  166. // 选择器占位符
  167. placeholder: {
  168. type: String,
  169. default: () => t("请选择")
  170. },
  171. // 是否显示选择器触发器
  172. showTrigger: {
  173. type: Boolean,
  174. default: true
  175. },
  176. // 是否禁用选择器
  177. disabled: {
  178. type: Boolean,
  179. default: false
  180. },
  181. // 确认按钮文本
  182. confirmText: {
  183. type: String,
  184. default: () => t("确定")
  185. },
  186. // 是否显示确认按钮
  187. showConfirm: {
  188. type: Boolean,
  189. default: true
  190. },
  191. // 取消按钮文本
  192. cancelText: {
  193. type: String,
  194. default: () => t("取消")
  195. },
  196. // 是否显示取消按钮
  197. showCancel: {
  198. type: Boolean,
  199. default: true
  200. },
  201. // 标签格式化
  202. labelFormat: {
  203. type: String as PropType<string>,
  204. default: ""
  205. },
  206. // 值格式化
  207. valueFormat: {
  208. type: String as PropType<string>,
  209. default: ""
  210. },
  211. // 开始日期
  212. start: {
  213. type: String,
  214. default: config.startDate
  215. },
  216. // 结束日期
  217. end: {
  218. type: String,
  219. default: config.endDate
  220. },
  221. // 类型,控制选择的粒度
  222. type: {
  223. type: String as PropType<"year" | "month" | "date" | "hour" | "minute" | "second">,
  224. default: "second"
  225. },
  226. // 是否范围选择
  227. rangeable: {
  228. type: Boolean,
  229. default: false
  230. },
  231. // 开始日期占位符
  232. startPlaceholder: {
  233. type: String,
  234. default: () => t("开始日期")
  235. },
  236. // 结束日期占位符
  237. endPlaceholder: {
  238. type: String,
  239. default: () => t("结束日期")
  240. },
  241. // 范围分隔符
  242. rangeSeparator: {
  243. type: String,
  244. default: () => t(" 至 ")
  245. },
  246. // 是否显示快捷选项
  247. showShortcuts: {
  248. type: Boolean,
  249. default: true
  250. },
  251. // 快捷选项
  252. shortcuts: {
  253. type: Array as PropType<ClSelectDateShortcut[]>,
  254. default: () => []
  255. }
  256. });
  257. // 定义事件
  258. const emit = defineEmits(["update:modelValue", "change", "update:values", "range-change"]);
  259. const ui = useUi();
  260. // 弹出层引用,用于控制popup的显示与隐藏
  261. const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
  262. // 透传样式类型定义
  263. type PassThrough = {
  264. trigger?: ClSelectTriggerPassThrough;
  265. popup?: ClPopupPassThrough;
  266. };
  267. // 解析透传样式配置,返回合并后的样式对象
  268. const pt = computed(() => parsePt<PassThrough>(props.pt));
  269. // 格式化类型
  270. const formatType = computed(() => {
  271. switch (props.type) {
  272. case "year":
  273. return "YYYY";
  274. case "month":
  275. return "YYYY-MM";
  276. case "date":
  277. return "YYYY-MM-DD";
  278. case "hour":
  279. case "minute":
  280. case "second":
  281. return "YYYY-MM-DD HH:mm:ss";
  282. default:
  283. return "YYYY-MM-DD HH:mm:ss";
  284. }
  285. });
  286. // 标签格式化
  287. const labelFormat = computed(() => {
  288. if (isNull(props.labelFormat) || isEmpty(props.labelFormat)) {
  289. return formatType.value;
  290. }
  291. return props.labelFormat;
  292. });
  293. // 值格式化
  294. const valueFormat = computed(() => {
  295. if (isNull(props.valueFormat) || isEmpty(props.valueFormat)) {
  296. return formatType.value;
  297. }
  298. return props.valueFormat;
  299. });
  300. // 快捷选项索引
  301. const shortcutsIndex = ref<number>(-1);
  302. // 快捷选项列表
  303. const shortcuts = computed<ClSelectDateShortcut[]>(() => {
  304. if (!isEmpty(props.shortcuts)) {
  305. return props.shortcuts;
  306. }
  307. return [
  308. {
  309. label: t("今天"),
  310. value: [dayUts().format(valueFormat.value), dayUts().format(valueFormat.value)]
  311. },
  312. {
  313. label: t("近7天"),
  314. value: [
  315. dayUts().subtract(7, "day").format(valueFormat.value),
  316. dayUts().format(valueFormat.value)
  317. ]
  318. },
  319. {
  320. label: t("近30天"),
  321. value: [
  322. dayUts().subtract(30, "day").format(valueFormat.value),
  323. dayUts().format(valueFormat.value)
  324. ]
  325. },
  326. {
  327. label: t("近90天"),
  328. value: [
  329. dayUts().subtract(90, "day").format(valueFormat.value),
  330. dayUts().format(valueFormat.value)
  331. ]
  332. },
  333. {
  334. label: t("近一年"),
  335. value: [
  336. dayUts().subtract(1, "year").format(valueFormat.value),
  337. dayUts().format(valueFormat.value)
  338. ]
  339. }
  340. ];
  341. });
  342. // 范围值索引,0为开始日期,1为结束日期
  343. const rangeIndex = ref<number>(0);
  344. // 范围值,依次为开始日期、结束日期
  345. const values = ref<string[]>(["", ""]);
  346. // 当前选中的值,存储为数组,依次为年月日时分秒
  347. const value = ref<number[]>([]);
  348. // 开始日期
  349. const start = computed(() => {
  350. if (props.rangeable) {
  351. if (rangeIndex.value == 0) {
  352. return props.start;
  353. } else {
  354. return values.value[0];
  355. }
  356. } else {
  357. return props.start;
  358. }
  359. });
  360. // 时间选择器列表,动态生成每一列的选项
  361. const list = computed(() => {
  362. // 解析开始日期为年月日时分秒数组
  363. const [startYear, startMonth, startDate, startHour, startMinute, startSecond] = dayUts(
  364. start.value
  365. ).toArray();
  366. // 解析结束日期为年月日时分秒数组
  367. const [endYear, endMonth, endDate, endHour, endMinute, endSecond] = dayUts(props.end).toArray();
  368. // 初始化年月日时分秒六个选项数组
  369. const arr = [[], [], [], [], [], []] as ClSelectOption[][];
  370. // 边界处理,如果value为空,返回空数组
  371. if (isEmpty(value.value)) {
  372. return arr;
  373. }
  374. // 获取当前选中的年月日时分秒值
  375. const [year, month, date, hour, minute] = value.value;
  376. // 判断是否为闰年
  377. const isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
  378. // 根据月份和是否闰年获取当月天数
  379. const days = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][
  380. month > 0 ? month - 1 : 0
  381. ];
  382. // 计算年份范围,确保至少有60年可选
  383. const yearRange = Math.max(60, endYear - startYear + 1);
  384. // 遍历生成年月日时分秒的选项
  385. for (let i = 0; i < yearRange; i++) {
  386. // 计算当前遍历的年份
  387. const yearNum = startYear + i;
  388. // 如果年份在结束年份范围内,添加到年份选项
  389. if (yearNum <= endYear) {
  390. arr[0].push({
  391. label: yearNum.toString(),
  392. value: yearNum
  393. });
  394. }
  395. // 处理月份选项
  396. let monthNum = startYear == year ? startMonth + i : i + 1;
  397. let endMonthNum = endYear == year ? endMonth : 12;
  398. // 添加有效的月份选项
  399. if (monthNum <= endMonthNum) {
  400. arr[1].push({
  401. label: monthNum.toString().padStart(2, "0"),
  402. value: monthNum
  403. });
  404. }
  405. // 处理日期选项
  406. let dateNum = startYear == year && startMonth == month ? startDate + i : i + 1;
  407. let endDateNum = endYear == year && endMonth == month ? endDate : days;
  408. // 添加有效的日期选项
  409. if (dateNum <= endDateNum) {
  410. arr[2].push({
  411. label: dateNum.toString().padStart(2, "0"),
  412. value: dateNum
  413. });
  414. }
  415. // 处理小时选项
  416. let hourNum =
  417. startYear == year && startMonth == month && startDate == date ? startHour + i : i;
  418. let endHourNum = endYear == year && endMonth == month && endDate == date ? endHour : 24;
  419. // 添加有效的小时选项
  420. if (hourNum < endHourNum) {
  421. arr[3].push({
  422. label: hourNum.toString().padStart(2, "0"),
  423. value: hourNum
  424. });
  425. }
  426. // 处理分钟选项
  427. let minuteNum =
  428. startYear == year && startMonth == month && startDate == date && startHour == hour
  429. ? startMinute + i
  430. : i;
  431. let endMinuteNum =
  432. endYear == year && endMonth == month && endDate == date && endHour == hour
  433. ? endMinute
  434. : 60;
  435. // 添加有效的分钟选项
  436. if (minuteNum < endMinuteNum) {
  437. arr[4].push({
  438. label: minuteNum.toString().padStart(2, "0"),
  439. value: minuteNum
  440. });
  441. }
  442. // 处理秒钟选项
  443. let secondNum =
  444. startYear == year &&
  445. startMonth == month &&
  446. startDate == date &&
  447. startHour == hour &&
  448. startMinute == minute
  449. ? startSecond + i
  450. : i;
  451. let endSecondNum =
  452. endYear == year &&
  453. endMonth == month &&
  454. endDate == date &&
  455. endHour == hour &&
  456. endMinute == minute
  457. ? endSecond
  458. : 60;
  459. // 添加有效的秒钟选项
  460. if (secondNum < endSecondNum) {
  461. arr[5].push({
  462. label: secondNum.toString().padStart(2, "0"),
  463. value: secondNum
  464. });
  465. }
  466. }
  467. // 返回包含所有时间选项的数组
  468. return arr;
  469. });
  470. // 列数,决定显示多少列(年、月、日、时、分、秒)
  471. const columnNum = computed(() => {
  472. return (
  473. ["year", "month", "date", "hour", "minute", "second"].findIndex((e) => e == props.type) + 1
  474. );
  475. });
  476. // 列数据,取出需要显示的列
  477. const columns = computed(() => {
  478. return list.value.slice(0, columnNum.value);
  479. });
  480. // 当前选中项的索引,返回每一列当前选中的下标
  481. const indexes = computed(() => {
  482. // 如果当前值为空,返回空数组
  483. if (isEmpty(value.value)) {
  484. return [];
  485. }
  486. // 遍历每一列,查找当前值在选项中的下标
  487. return value.value.map((e, i) => {
  488. let index = list.value[i].findIndex((a) => a.value == e) as number;
  489. // 如果未找到,返回最后一个
  490. if (index == -1) {
  491. index = list.value[i].length - 1;
  492. }
  493. // 如果小于0,返回0
  494. if (index < 0) {
  495. index = 0;
  496. }
  497. return index;
  498. });
  499. });
  500. // 将当前选中的年月日时分秒拼接为字符串
  501. function toDate() {
  502. // 使用数组存储日期时间各部分,避免重复字符串拼接
  503. const parts: string[] = [];
  504. // 月日时分秒需要补0对齐
  505. const units = ["", "-", "-", " ", ":", ":"];
  506. // 默认值
  507. const defaultValue = [2000, 1, 1, 0, 0, 0];
  508. // 遍历处理各个时间单位
  509. units.forEach((key, i) => {
  510. let val = value.value[i];
  511. // 超出当前列数时,使用默认值
  512. if (i >= columnNum.value) {
  513. val = defaultValue[i];
  514. }
  515. // 拼接字符串并补0
  516. parts.push(key + val.toString().padStart(2, "0"));
  517. });
  518. // 拼接所有部分返回
  519. return parts.join("");
  520. }
  521. // 检查边界值
  522. function checkDate(values: number[]): number[] {
  523. if (values.length == 0) {
  524. return values;
  525. }
  526. // 确保至少有6个元素,缺失的用默认值填充
  527. const checkedValues = [...values];
  528. const defaultValues = [2000, 1, 1, 0, 0, 0];
  529. for (let i = checkedValues.length; i < 6; i++) {
  530. checkedValues.push(defaultValues[i]);
  531. }
  532. let [year, month, date, hour, minute, second] = checkedValues;
  533. // 检查日期边界(根据年份和月份确定最大天数)
  534. // 判断是否为闰年
  535. const isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
  536. // 每月天数数组,2月根据闰年判断
  537. const daysInMonth = [31, isLeapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  538. const maxDay = daysInMonth[month - 1];
  539. if (date < 1) {
  540. date = 1;
  541. } else if (date > maxDay) {
  542. date = maxDay;
  543. }
  544. // 检查小时边界 (0-23)
  545. if (hour < 0) {
  546. hour = 0;
  547. } else if (hour > 23) {
  548. hour = 23;
  549. }
  550. // 检查分钟边界 (0-59)
  551. if (minute < 0) {
  552. minute = 0;
  553. } else if (minute > 59) {
  554. minute = 59;
  555. }
  556. // 检查秒钟边界 (0-59)
  557. if (second < 0) {
  558. second = 0;
  559. } else if (second > 59) {
  560. second = 59;
  561. }
  562. return [year, month, date, hour, minute, second];
  563. }
  564. // 显示文本
  565. const text = ref("");
  566. // 更新文本内容
  567. function updateText() {
  568. if (props.rangeable) {
  569. text.value = values.value
  570. .map((e) => dayUts(e).format(labelFormat.value))
  571. .join(` ${props.rangeSeparator} `);
  572. } else {
  573. text.value = dayUts(toDate()).format(labelFormat.value);
  574. }
  575. }
  576. // 选择器值改变事件,更新value
  577. async function onChange(data: number[]) {
  578. // 更新value
  579. value.value = checkDate(data);
  580. // 不能大于结束日期
  581. if (dayUts(toDate()).isAfter(dayUts(props.end))) {
  582. value.value = dayUts(props.end).toArray();
  583. }
  584. // 不能小于开始日期
  585. if (dayUts(toDate()).isBefore(dayUts(props.start))) {
  586. value.value = dayUts(props.start).toArray();
  587. }
  588. // 设置范围值
  589. if (props.rangeable) {
  590. values.value[rangeIndex.value] = dayUts(toDate()).format(valueFormat.value);
  591. // 判断开始日期是否大于结束日期
  592. if (dayUts(values.value[0]).isAfter(dayUts(values.value[1])) && values.value[1] != "") {
  593. values.value[1] = values.value[0];
  594. }
  595. // 重置快捷选项索引
  596. shortcutsIndex.value = -1;
  597. }
  598. }
  599. // 设置value
  600. function setValue(val: string) {
  601. text.value = "";
  602. // 如果值为空,使用当前时间
  603. if (isNull(val) || isEmpty(val)) {
  604. value.value = checkDate(dayUts().toArray());
  605. } else {
  606. // 否则解析为数组
  607. value.value = checkDate(dayUts(val).toArray());
  608. updateText();
  609. }
  610. }
  611. // 设置values
  612. function setValues(val: string[]) {
  613. if (isEmpty(val)) {
  614. values.value = ["", ""];
  615. } else {
  616. values.value = val;
  617. }
  618. }
  619. // 设置范围值索引
  620. function setRange(index: number) {
  621. rangeIndex.value = index;
  622. setValue(values.value[index]);
  623. }
  624. // 设置范围值
  625. function setRangeValue(val: string[], index: number) {
  626. shortcutsIndex.value = index;
  627. values.value = [...val] as string[];
  628. setValue(val[rangeIndex.value]);
  629. }
  630. // 选择器显示状态,控制popup显示
  631. const visible = ref(false);
  632. // 选择回调函数
  633. let callback: ((value: string | string[]) => void) | null = null;
  634. // 打开选择器
  635. function open(cb: ((value: string | string[]) => void) | null = null) {
  636. // 如果组件被禁用,则不执行后续操作,直接返回
  637. if (props.disabled) {
  638. return;
  639. }
  640. // 显示选择器弹窗
  641. visible.value = true;
  642. // 保存回调函数
  643. callback = cb;
  644. nextTick(() => {
  645. if (props.rangeable) {
  646. // 如果是范围选择,初始化为选择开始时间
  647. rangeIndex.value = 0;
  648. // 设置范围值
  649. setValues(props.values);
  650. // 设置当前选中的值为范围的开始值
  651. setValue(values.value[0]);
  652. } else {
  653. // 非范围选择,设置当前选中的值为modelValue
  654. setValue(props.modelValue);
  655. }
  656. });
  657. }
  658. // 关闭选择器,设置visible为false
  659. function close() {
  660. visible.value = false;
  661. }
  662. // 选择器关闭后
  663. function onClosed() {
  664. values.value = ["", ""];
  665. }
  666. // 清空选择器,重置显示文本并触发事件
  667. function clear() {
  668. text.value = "";
  669. if (props.rangeable) {
  670. emit("update:values", [] as string[]);
  671. emit("range-change", [] as string[]);
  672. } else {
  673. emit("update:modelValue", "");
  674. emit("change", "");
  675. }
  676. }
  677. // 确认选择,触发事件并关闭选择器
  678. function confirm() {
  679. if (props.rangeable) {
  680. const [a, b] = values.value;
  681. if (a == "" || b == "") {
  682. ui.showToast({
  683. message: t("请选择完整时间范围")
  684. });
  685. if (a != "") {
  686. rangeIndex.value = 1;
  687. }
  688. return;
  689. }
  690. if (dayUts(a).isAfter(dayUts(b))) {
  691. ui.showToast({
  692. message: t("开始日期不能大于结束日期")
  693. });
  694. return;
  695. }
  696. // 触发更新事件
  697. emit("update:values", values.value);
  698. emit("range-change", values.value);
  699. // 触发回调
  700. if (callback != null) {
  701. callback!(values.value as string[]);
  702. }
  703. } else {
  704. const val = dayUts(toDate()).format(valueFormat.value);
  705. // 触发更新事件
  706. emit("update:modelValue", val);
  707. emit("change", val);
  708. // 触发回调
  709. if (callback != null) {
  710. callback!(val);
  711. }
  712. }
  713. // 更新显示文本
  714. updateText();
  715. // 关闭选择器
  716. close();
  717. }
  718. // 监听modelValue变化
  719. watch(
  720. computed(() => props.modelValue),
  721. (val: string) => {
  722. setValue(val);
  723. },
  724. {
  725. immediate: true
  726. }
  727. );
  728. // 监听values变化
  729. watch(
  730. computed(() => props.values),
  731. (val: string[]) => {
  732. setValues(val);
  733. },
  734. {
  735. immediate: true
  736. }
  737. );
  738. // 更新显示文本
  739. watch(
  740. computed(() => props.labelFormat),
  741. () => {
  742. updateText();
  743. }
  744. );
  745. defineExpose({
  746. open,
  747. close,
  748. clear,
  749. confirm,
  750. setValue,
  751. setValues,
  752. setRange
  753. });
  754. </script>
  755. <style lang="scss" scoped>
  756. .cl-select {
  757. &-popup {
  758. &__op {
  759. @apply flex flex-row items-center justify-center;
  760. padding: 24rpx;
  761. }
  762. &__range {
  763. @apply px-3 pt-2 pb-5;
  764. &-values {
  765. @apply flex flex-row items-center justify-center;
  766. &-start,
  767. &-end {
  768. @apply flex-1 bg-surface-50 rounded-xl border border-solid border-surface-200;
  769. @apply py-2;
  770. &.is-dark {
  771. @apply border-surface-500 bg-surface-700;
  772. }
  773. &.active {
  774. @apply border-primary-500 bg-transparent;
  775. }
  776. }
  777. }
  778. &-shortcuts {
  779. @apply flex flex-row flex-wrap items-center mb-4;
  780. }
  781. }
  782. }
  783. }
  784. </style>