| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- <template>
- <view
- class="cl-switch"
- :class="[
- {
- 'cl-switch--disabled': disabled,
- 'cl-switch--checked': isChecked
- },
- pt.className
- ]"
- @tap="onTap"
- >
- <view
- class="cl-switch__track"
- :class="[
- {
- 'is-checked': isChecked,
- 'is-dark': isDark
- },
- pt.track?.className
- ]"
- :style="{
- height: rect.height,
- width: rect.width
- }"
- >
- <view
- class="cl-switch__thumb"
- :class="[pt.thumb?.className]"
- :style="{
- height: rect.size,
- width: rect.size,
- left: rect.left,
- transform: `translateX(${isChecked ? rect.translateX : 0})`
- }"
- >
- <cl-loading
- v-if="loading"
- :size="24"
- color="primary"
- :pt="{
- className: parseClass([pt.loading?.className])
- }"
- ></cl-loading>
- </view>
- </view>
- </view>
- </template>
- <script lang="ts" setup>
- import { computed, ref, watch } from "vue";
- import { isDark, parseClass, parsePt } from "@/cool";
- import type { PassThroughProps } from "../../types";
- import { vibrate } from "@/uni_modules/cool-vibrate";
- defineOptions({
- name: "cl-switch"
- });
- // 定义组件属性
- const props = defineProps({
- // 透传样式配置
- pt: {
- type: Object,
- default: () => ({})
- },
- // 绑定值 - 开关状态
- modelValue: {
- type: Boolean,
- default: false
- },
- // 是否禁用
- disabled: {
- type: Boolean,
- default: false
- },
- // 加载状态
- loading: {
- type: Boolean,
- default: false
- },
- // 高度
- height: {
- type: Number,
- default: 48
- },
- // 宽度
- width: {
- type: Number,
- default: 80
- }
- });
- // 定义组件事件
- const emit = defineEmits(["update:modelValue", "change"]);
- // 透传样式类型定义
- type PassThrough = {
- className?: string;
- track?: PassThroughProps;
- thumb?: PassThroughProps;
- label?: PassThroughProps;
- loading?: PassThroughProps;
- };
- // 解析透传样式配置
- const pt = computed(() => parsePt<PassThrough>(props.pt));
- // 绑定值
- const value = ref(props.modelValue);
- // 是否为选中状态
- const isChecked = computed(() => value.value);
- // 计算开关组件的尺寸样式
- type Rect = {
- height: string;
- width: string;
- size: string;
- left: string;
- translateX: string;
- };
- const rect = computed<Rect>(() => {
- // 获取开关轨道高度
- const height = props.height;
- // 获取开关轨道宽度
- const width = props.width;
- // 计算圆形按钮尺寸,比轨道高度小8rpx
- const size = height - 8;
- // 设置圆形按钮初始位置,距离左侧px
- const left = 4;
- // 计算圆形按钮移动距离,为轨道宽度减去轨道高度
- const translateX = width - height;
- return {
- height: height + "rpx",
- width: width + "rpx",
- size: size + "rpx",
- left: left + "rpx",
- translateX: translateX + "rpx"
- };
- });
- /**
- * 点击事件处理函数
- * 在非禁用且非加载状态下切换开关状态
- */
- function onTap() {
- if (!props.disabled && !props.loading) {
- // 切换开关状态
- const val = !value.value;
- value.value = val;
- // 触发更新事件
- emit("update:modelValue", val);
- emit("change", val);
- // 震动
- vibrate(1);
- }
- }
- watch(
- computed(() => props.modelValue),
- (val: boolean) => {
- value.value = val;
- }
- );
- </script>
- <style lang="scss" scoped>
- .cl-switch {
- @apply flex flex-row items-center duration-200;
- &__track {
- @apply flex flex-row items-center relative rounded-full duration-200;
- @apply bg-surface-200;
- &.is-dark {
- @apply bg-surface-500;
- }
- &.is-checked {
- @apply bg-primary-500;
- }
- }
- &__thumb {
- @apply flex items-center justify-center absolute;
- @apply bg-white rounded-full duration-300;
- }
- &.cl-switch--disabled {
- @apply opacity-50;
- }
- }
- </style>
|