cl-calendar-select.uvue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. @open="open()"
  10. @clear="clear"
  11. ></cl-select-trigger>
  12. <cl-popup ref="popupRef" v-model="visible" :title="title" :pt="ptPopup">
  13. <view class="cl-select-popup" @touchmove.stop>
  14. <slot name="prepend"></slot>
  15. <view class="cl-select-popup__picker">
  16. <cl-calendar
  17. v-model="value"
  18. v-model:date="date"
  19. :mode="mode"
  20. :date-config="dateConfig"
  21. :start="start"
  22. :end="end"
  23. @change="onCalendarChange"
  24. ></cl-calendar>
  25. </view>
  26. <slot name="append"></slot>
  27. <view class="cl-select-popup__op">
  28. <cl-button
  29. v-if="showCancel"
  30. size="large"
  31. text
  32. border
  33. type="light"
  34. :pt="{
  35. className: 'flex-1 !rounded-xl h-[80rpx]'
  36. }"
  37. @tap="close"
  38. >{{ cancelText }}</cl-button
  39. >
  40. <cl-button
  41. v-if="showConfirm"
  42. size="large"
  43. :pt="{
  44. className: 'flex-1 !rounded-xl h-[80rpx]'
  45. }"
  46. @tap="confirm"
  47. >{{ confirmText }}</cl-button
  48. >
  49. </view>
  50. </view>
  51. </cl-popup>
  52. </template>
  53. <script setup lang="ts">
  54. import { ref, computed, type PropType } from "vue";
  55. import type { ClCalendarDateConfig, ClCalendarMode } from "../../types";
  56. import { isEmpty, parsePt, parseToObject } from "@/cool";
  57. import type { ClSelectTriggerPassThrough } from "../cl-select-trigger/props";
  58. import type { ClPopupPassThrough } from "../cl-popup/props";
  59. import { t } from "@/locale";
  60. import { useUi } from "../../hooks";
  61. defineOptions({
  62. name: "cl-calendar-select"
  63. });
  64. defineSlots<{
  65. prepend(): any;
  66. append(): any;
  67. }>();
  68. // 组件属性定义
  69. const props = defineProps({
  70. // 透传样式配置
  71. pt: {
  72. type: Object,
  73. default: () => ({})
  74. },
  75. // 选择器的值
  76. modelValue: {
  77. type: String as PropType<string | null>,
  78. default: null
  79. },
  80. // 多个日期
  81. date: {
  82. type: Array as PropType<string[]>,
  83. default: () => []
  84. },
  85. // 日期选择模式
  86. mode: {
  87. type: String as PropType<ClCalendarMode>,
  88. default: "single"
  89. },
  90. // 日期配置
  91. dateConfig: {
  92. type: Array as PropType<ClCalendarDateConfig[]>,
  93. default: () => []
  94. },
  95. // 开始日期,可选日期的开始
  96. start: {
  97. type: String
  98. },
  99. // 结束日期,可选日期的结束
  100. end: {
  101. type: String
  102. },
  103. // 选择器标题
  104. title: {
  105. type: String,
  106. default: () => t("请选择")
  107. },
  108. // 选择器占位符
  109. placeholder: {
  110. type: String,
  111. default: () => t("请选择")
  112. },
  113. // 是否显示选择器触发器
  114. showTrigger: {
  115. type: Boolean,
  116. default: true
  117. },
  118. // 是否禁用选择器
  119. disabled: {
  120. type: Boolean,
  121. default: false
  122. },
  123. // 分隔符
  124. splitor: {
  125. type: String,
  126. default: "、"
  127. },
  128. // 范围分隔符
  129. rangeSplitor: {
  130. type: String,
  131. default: () => t(" 至 ")
  132. },
  133. // 确认按钮文本
  134. confirmText: {
  135. type: String,
  136. default: () => t("确定")
  137. },
  138. // 是否显示确认按钮
  139. showConfirm: {
  140. type: Boolean,
  141. default: true
  142. },
  143. // 取消按钮文本
  144. cancelText: {
  145. type: String,
  146. default: () => t("取消")
  147. },
  148. // 是否显示取消按钮
  149. showCancel: {
  150. type: Boolean,
  151. default: true
  152. }
  153. });
  154. // 定义事件
  155. const emit = defineEmits(["update:modelValue", "update:date", "change", "select"]);
  156. // UI实例
  157. const ui = useUi();
  158. // 弹出层引用
  159. const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
  160. // 透传样式类型定义
  161. type PassThrough = {
  162. trigger?: ClSelectTriggerPassThrough;
  163. popup?: ClPopupPassThrough;
  164. };
  165. // 解析透传样式配置
  166. const pt = computed(() => parsePt<PassThrough>(props.pt));
  167. // 解析触发器透传样式配置
  168. const ptTrigger = computed(() => parseToObject(pt.value.trigger));
  169. // 解析弹窗透传样式配置
  170. const ptPopup = computed(() => parseToObject(pt.value.popup));
  171. // 当前选中的值
  172. const value = ref<string | null>(null);
  173. // 当前选中的日期
  174. const date = ref<string[]>([]);
  175. // 显示文本
  176. const text = computed(() => {
  177. switch (props.mode) {
  178. case "single":
  179. return props.modelValue ?? "";
  180. case "multiple":
  181. return props.date.join(props.splitor);
  182. case "range":
  183. return props.date.join(props.rangeSplitor);
  184. default:
  185. return "";
  186. }
  187. });
  188. // 选择器显示状态
  189. const visible = ref(false);
  190. // 选择回调函数
  191. let callback: ((value: string | string[]) => void) | null = null;
  192. // 打开选择器
  193. function open(cb: ((value: string | string[]) => void) | null = null) {
  194. visible.value = true;
  195. // 单选日期
  196. value.value = props.modelValue;
  197. // 多个日期
  198. date.value = props.date;
  199. // 回调
  200. callback = cb;
  201. }
  202. // 关闭选择器
  203. function close() {
  204. visible.value = false;
  205. }
  206. // 清空选择器
  207. function clear() {
  208. value.value = null;
  209. date.value = [] as string[];
  210. emit("update:modelValue", value.value);
  211. emit("update:date", date.value);
  212. }
  213. // 确认选择
  214. function confirm() {
  215. if (props.mode == "single") {
  216. if (value.value == null) {
  217. ui.showToast({
  218. message: t("请选择日期")
  219. });
  220. return;
  221. }
  222. emit("update:modelValue", value.value);
  223. emit("change", value.value);
  224. // 触发回调
  225. if (callback != null) {
  226. callback!(value.value);
  227. }
  228. } else {
  229. if (isEmpty(date.value)) {
  230. ui.showToast({
  231. message: t("请选择日期")
  232. });
  233. return;
  234. }
  235. if (date.value.length != 2) {
  236. ui.showToast({
  237. message: t("请选择日期范围")
  238. });
  239. return;
  240. }
  241. emit("update:date", date.value);
  242. emit("change", date.value);
  243. // 触发回调
  244. if (callback != null) {
  245. callback!(date.value);
  246. }
  247. }
  248. // 关闭选择器
  249. close();
  250. }
  251. // 日历变化
  252. function onCalendarChange(date: string[]) {
  253. emit("select", date);
  254. }
  255. defineExpose({
  256. open,
  257. close
  258. });
  259. </script>
  260. <style lang="scss" scoped>
  261. .cl-select {
  262. &-popup {
  263. &__picker {
  264. @apply p-3 pt-0;
  265. }
  266. &__op {
  267. @apply flex flex-row items-center justify-center;
  268. padding: 24rpx;
  269. }
  270. }
  271. }
  272. </style>