cl-checkbox.uvue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <template>
  2. <view
  3. class="cl-checkbox"
  4. :class="[
  5. {
  6. 'cl-checkbox--disabled': isDisabled,
  7. 'cl-checkbox--checked': isChecked
  8. },
  9. pt.className
  10. ]"
  11. @tap="onTap"
  12. >
  13. <cl-icon
  14. v-if="showIcon"
  15. :name="iconName"
  16. :size="pt.icon?.size ?? 40"
  17. :pt="{
  18. className: parseClass([
  19. 'cl-checkbox__icon mr-1',
  20. {
  21. '!text-primary-500': isChecked
  22. },
  23. pt.icon?.className
  24. ])
  25. }"
  26. ></cl-icon>
  27. <cl-text
  28. :pt="{
  29. className: parseClass([
  30. 'cl-checkbox__label',
  31. {
  32. '!text-primary-500': isChecked
  33. },
  34. pt.label?.className
  35. ])
  36. }"
  37. v-if="showLabel"
  38. >
  39. <slot>{{ label }}</slot>
  40. </cl-text>
  41. </view>
  42. </template>
  43. <script lang="ts" setup>
  44. import { computed, useSlots, type PropType } from "vue";
  45. import type { PassThroughProps } from "../../types";
  46. import { get, parseClass, parsePt, pull } from "@/cool";
  47. import type { ClIconProps } from "../cl-icon/props";
  48. import { useForm } from "../../hooks";
  49. defineOptions({
  50. name: "cl-checkbox"
  51. });
  52. // 定义组件属性
  53. const props = defineProps({
  54. // 透传样式配置
  55. pt: {
  56. type: Object,
  57. default: () => ({})
  58. },
  59. // 绑定值 - 当前选中的值
  60. modelValue: {
  61. type: [Array, Boolean] as PropType<any[] | boolean>,
  62. default: () => []
  63. },
  64. // 标签文本
  65. label: {
  66. type: String,
  67. default: ""
  68. },
  69. // 选项值 - 该单选框对应的值
  70. value: {
  71. type: null
  72. },
  73. // 是否禁用
  74. disabled: {
  75. type: Boolean,
  76. default: false
  77. },
  78. // 选中时的图标
  79. activeIcon: {
  80. type: String,
  81. default: "checkbox-line"
  82. },
  83. // 未选中时的图标
  84. inactiveIcon: {
  85. type: String,
  86. default: "checkbox-blank-line"
  87. },
  88. // 是否显示图标
  89. showIcon: {
  90. type: Boolean,
  91. default: true
  92. }
  93. });
  94. // 定义组件事件
  95. const emit = defineEmits(["update:modelValue", "change"]);
  96. const slots = useSlots();
  97. const { disabled } = useForm();
  98. // 是否禁用
  99. const isDisabled = computed(() => props.disabled || disabled.value);
  100. // 透传样式类型定义
  101. type PassThrough = {
  102. className?: string;
  103. icon?: ClIconProps;
  104. label?: PassThroughProps;
  105. };
  106. // 解析透传样式配置
  107. const pt = computed(() => parsePt<PassThrough>(props.pt));
  108. // 是否为选中状态
  109. const isChecked = computed(() => {
  110. if (Array.isArray(props.modelValue)) {
  111. return props.modelValue.includes(props.value!);
  112. }
  113. if (typeof props.modelValue == "boolean") {
  114. return props.modelValue;
  115. }
  116. return false;
  117. });
  118. // 是否显示标签
  119. const showLabel = computed(() => props.label != "" || get(slots, "default") != null);
  120. // 图标名称
  121. const iconName = computed(() => {
  122. // 选中状态
  123. if (isChecked.value) {
  124. return props.activeIcon;
  125. }
  126. // 默认状态
  127. return props.inactiveIcon;
  128. });
  129. /**
  130. * 点击事件处理函数
  131. * 在非禁用状态下切换选中状态
  132. */
  133. function onTap() {
  134. if (!isDisabled.value) {
  135. let val = props.modelValue;
  136. if (Array.isArray(val)) {
  137. if (isChecked.value) {
  138. val = pull(val, props.value!);
  139. } else {
  140. val.push(props.value!);
  141. }
  142. } else {
  143. val = !val;
  144. }
  145. emit("update:modelValue", val);
  146. emit("change", val);
  147. }
  148. }
  149. </script>
  150. <style lang="scss" scoped>
  151. .cl-checkbox {
  152. @apply flex flex-row items-center;
  153. &--disabled {
  154. @apply opacity-50;
  155. }
  156. }
  157. </style>