SlidingSwitch.vue 3.72 KB
<script lang="ts">
  export default {
    components: { Spin },
    inheritAttrs: false,
  };
</script>
<script lang="ts" setup>
  import { Spin } from 'ant-design-vue';
  import { RadioRecord } from '../../detail/config/util';
  import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';
  import { useSendCommand } from './useSendCommand';
  import { ref } from 'vue';

  interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> {
    value?: Value;
    layout?: Layout;
    radio?: RadioRecord;
    random?: boolean;
    add?: (key: string, method: Fn) => void;
    update?: () => void;
    remove?: (key: string) => void;
  }

  const props = defineProps<VisualComponentProps>();

  const emit = defineEmits(['update:value', 'change']);

  const { sendCommand } = useSendCommand();

  const loading = ref(false);
  const handleChange = async (event: Event) => {
    const _value = (event.target as HTMLInputElement).checked;
    if (props.value) {
      loading.value = true;
      const flag = await sendCommand(props.value, _value);
      loading.value = false;
      if (!flag) {
        (event.target as HTMLInputElement).checked = !_value;
        return;
      }
    }
    emit('update:value', _value);
    emit('change', _value);
  };
</script>

<template>
  <div class="flex flex-col justify-center">
    <Spin :spinning="loading">
      <label class="sliding-switch">
        <input
          :value="!!Number(props.value?.value)"
          type="checkbox"
          :checked="!!Number(props.value?.value)"
          @change="handleChange"
        />
        <span class="slider"></span>
        <span class="on">ON</span>
        <span class="off">OFF</span>
      </label>
      <div
        class="text-center mt-2 text-gray-700"
        :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"
      >
        {{ props.value?.attributeRename || props.value?.attribute }}</div
      >
    </Spin>
  </div>
</template>

<style scoped lang="less">
  .sliding-switch {
    position: relative;
    display: block;
    font-weight: 700;
    line-height: 40px;
    width: 80px;
    height: 40px;
    font-size: 14px;
    cursor: pointer;
    user-select: none;

    input[type='checkbox'] {
      display: none;
    }

    .slider {
      width: 80px;
      height: 40px;
      display: flex;
      align-items: center;
      box-sizing: border-box;
      border: 2px solid #ecf0f3;
      border-radius: 20px;
      box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%),
        inset -2px -2px 8px #fff, inset -2px -2px 12px hsl(0deg 0% 100% / 50%),
        inset 2px 2px 4px hsl(0deg 0% 100% / 10%), inset 2px 2px 8px rgb(0 0 0 / 30%),
        2px 2px 8px rgb(0 0 0 / 30%);
      background-color: #ecf0f3;
      z-index: -1;
    }

    .slider::after {
      cursor: pointer;
      display: block;
      content: '';
      width: 24px;
      height: 24px;
      border-radius: 50%;
      margin-left: 6px;
      margin-right: 6px;
      background-color: #ecf0f3;
      box-shadow: -2px -2px 8px #fff, -2px -2px 12px hsl(0deg 0% 100% / 50%),
        inset 2px 2px 4px hsl(0deg 0% 100% / 10%), 2px 2px 8px rgb(0 0 0 / 30%);
      z-index: 999;
      transition: 0.5s;
    }

    input:checked ~ .off {
      opacity: 0;
    }

    input:checked ~ .slider::after {
      transform: translateX(35px);
    }

    input:not(:checked) ~ .on {
      opacity: 0;
      transform: translateX(0);
    }

    .on,
    .off {
      position: absolute;
      top: 0;
      display: inline-block;
      margin-left: 3px;
      width: 34px;
      text-align: center;
      transition: 0.2s;
    }

    .on {
      color: #039be5;
    }

    .off {
      right: 6px;
      color: #999;
    }
  }
</style>