cl-switch.uvue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <template>
  2. <view
  3. class="cl-switch"
  4. :class="[
  5. {
  6. 'cl-switch--disabled': disabled,
  7. 'cl-switch--checked': isChecked
  8. },
  9. pt.className
  10. ]"
  11. @tap="onTap"
  12. >
  13. <view
  14. class="cl-switch__track"
  15. :class="[
  16. {
  17. 'is-checked': isChecked,
  18. 'is-dark': isDark
  19. },
  20. pt.track?.className
  21. ]"
  22. :style="{
  23. height: rect.height,
  24. width: rect.width
  25. }"
  26. >
  27. <view
  28. class="cl-switch__thumb"
  29. :class="[pt.thumb?.className]"
  30. :style="{
  31. height: rect.size,
  32. width: rect.size,
  33. left: rect.left,
  34. transform: `translateX(${isChecked ? rect.translateX : 0})`
  35. }"
  36. >
  37. <cl-loading
  38. v-if="loading"
  39. size="12px"
  40. color="primary"
  41. :pt="{
  42. className: parseClass([pt.loading?.className])
  43. }"
  44. ></cl-loading>
  45. </view>
  46. </view>
  47. </view>
  48. </template>
  49. <script lang="ts" setup>
  50. import { computed, ref, watch } from "vue";
  51. import { isDark, parseClass, parsePt } from "@/cool";
  52. import type { PassThroughProps } from "../../types";
  53. import { vibrate } from "@/uni_modules/cool-vibrate";
  54. defineOptions({
  55. name: "cl-switch"
  56. });
  57. // 定义组件属性
  58. const props = defineProps({
  59. // 透传样式配置
  60. pt: {
  61. type: Object,
  62. default: () => ({})
  63. },
  64. // 绑定值 - 开关状态
  65. modelValue: {
  66. type: Boolean,
  67. default: false
  68. },
  69. // 是否禁用
  70. disabled: {
  71. type: Boolean,
  72. default: false
  73. },
  74. // 加载状态
  75. loading: {
  76. type: Boolean,
  77. default: false
  78. },
  79. // 高度
  80. height: {
  81. type: Number,
  82. default: 24
  83. },
  84. // 宽度
  85. width: {
  86. type: Number,
  87. default: 40
  88. }
  89. });
  90. // 定义组件事件
  91. const emit = defineEmits(["update:modelValue", "change"]);
  92. // 透传样式类型定义
  93. type PassThrough = {
  94. className?: string;
  95. track?: PassThroughProps;
  96. thumb?: PassThroughProps;
  97. label?: PassThroughProps;
  98. loading?: PassThroughProps;
  99. };
  100. // 解析透传样式配置
  101. const pt = computed(() => parsePt<PassThrough>(props.pt));
  102. // 绑定值
  103. const value = ref(props.modelValue);
  104. // 是否为选中状态
  105. const isChecked = computed(() => value.value);
  106. // 计算开关组件的尺寸样式
  107. type Rect = {
  108. height: string;
  109. width: string;
  110. size: string;
  111. left: string;
  112. translateX: string;
  113. };
  114. const rect = computed<Rect>(() => {
  115. // 获取开关轨道高度
  116. const height = props.height;
  117. // 获取开关轨道宽度
  118. const width = props.width;
  119. // 计算圆形按钮尺寸,比轨道高度小4px
  120. const size = height - 4;
  121. // 设置圆形按钮初始位置,距离左侧2px
  122. const left = 2;
  123. // 计算圆形按钮移动距离,为轨道宽度减去轨道高度
  124. const translateX = width - height;
  125. return {
  126. height: height + "px",
  127. width: width + "px",
  128. size: size + "px",
  129. left: left + "px",
  130. translateX: translateX + "px"
  131. };
  132. });
  133. /**
  134. * 点击事件处理函数
  135. * 在非禁用且非加载状态下切换开关状态
  136. */
  137. function onTap() {
  138. if (!props.disabled && !props.loading) {
  139. // 切换开关状态
  140. const val = !value.value;
  141. value.value = val;
  142. // 触发更新事件
  143. emit("update:modelValue", val);
  144. emit("change", val);
  145. // 震动
  146. vibrate(1);
  147. }
  148. }
  149. watch(
  150. computed(() => props.modelValue),
  151. (val: boolean) => {
  152. value.value = val;
  153. }
  154. );
  155. </script>
  156. <style lang="scss" scoped>
  157. .cl-switch {
  158. @apply flex flex-row items-center duration-200;
  159. &__track {
  160. @apply flex flex-row items-center relative rounded-full duration-200;
  161. @apply bg-surface-200;
  162. &.is-dark {
  163. @apply bg-surface-500;
  164. }
  165. &.is-checked {
  166. @apply bg-primary-500;
  167. }
  168. }
  169. &__thumb {
  170. @apply flex items-center justify-center absolute;
  171. @apply bg-white rounded-full duration-300;
  172. }
  173. &.cl-switch--disabled {
  174. @apply opacity-50;
  175. }
  176. }
  177. </style>