cl-select-date.uvue 19 KB

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