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

  const props = defineProps<{
    value?: ControlComponentValue;
    layout?: Recordable;
    radio?: RadioRecord;
  }>();

  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);
  };

  const getRadio = computed(() => {
    return props.radio! || DEFAULT_RADIO_RECORD;
  });
</script>

<template>
  <div class="flex flex-col">
    <Spin :spinning="loading">
      <div
        class="toggle-switch"
        :style="{
          width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }),
          height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }),
        }"
      >
        <label class="switch">
          <input
            :value="!!Number(props.value?.value)"
            type="checkbox"
            :checked="!!Number(props.value?.value)"
            @change="handleChange"
          />
          <div class="button">
            <div class="light"></div>
            <div class="dots"></div>
            <div class="characters"></div>
            <div class="shine"></div>
            <div class="shadow"></div>
          </div>
        </label>
      </div>
      <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>
  .toggle-switch {
    /* flex: 1 1 auto; */
    max-width: 75px;

    /* height: 97.5px; */
    display: flex;
  }

  .switch {
    background-color: black;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px black, inset 0 2px 2px -2px white,
      inset 0 0 2px 15px #47434c, inset 0 0 2px 22px black;
    border-radius: 5px;
    padding: 10px;
    perspective: 700px;
  }

  .switch input {
    display: none;
  }

  .switch input:checked + .button {
    transform: translateZ(20px) rotateX(25deg);
    box-shadow: 0 -5px 10px #ff1818;
  }

  .switch input:checked + .button .light {
    animation: flicker 0.2s infinite 0.3s;
  }

  .switch input:checked + .button .shine {
    opacity: 1;
  }

  .switch input:checked + .button .shadow {
    opacity: 0;
  }

  .switch .button {
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all 0.3s cubic-bezier(1, 0, 1, 1);
    transform-origin: center center -20px;
    transform: translateZ(20px) rotateX(-25deg);
    transform-style: preserve-3d;
    width: 100%;
    height: 100%;
    position: relative;
    cursor: pointer;
    background: linear-gradient(#980000 0%, #6f0000 30%, #6f0000 70%, #980000 100%);
    background-color: #9b0621;
    background-repeat: no-repeat;
  }

  .switch .button::before {
    content: '';
    background: linear-gradient(
          rgba(255, 255, 255, 0.8) 10%,
          rgba(255, 255, 255, 0.3) 30%,
          #650000 75%,
          #320000
        )
        50% 50%/97% 97%,
      #b10000;
    background-repeat: no-repeat;
    width: 100%;
    height: 30px;
    transform-origin: top;
    transform: rotateX(-90deg);
    position: absolute;
    top: 0;
  }

  .switch .button::after {
    content: '';
    background-image: linear-gradient(#650000, #320000);
    width: 100%;
    height: 30px;
    transform-origin: top;
    transform: translateY(30px) rotateX(-90deg);
    position: absolute;
    bottom: 0;
    box-shadow: 0 30px 8px 0 black, 0 60px 20px 0 rgb(0 0 0 / 50%);
  }

  .switch .light {
    opacity: 0;
    animation: light-off 1s;
    position: absolute;
    width: 80%;
    height: 80%;
    background-image: radial-gradient(#ffc97e, transparent 40%),
      radial-gradient(circle, #ff1818 50%, transparent 80%);
  }

  .switch .dots {
    position: absolute;
    width: 100%;
    height: 100%;
    background-image: radial-gradient(transparent 30%, rgba(101, 0, 0, 0.7) 70%);
    background-size: 10px 10px;
  }

  .switch .characters {
    position: absolute;
    width: 100%;
    height: 100%;
    background: linear-gradient(white, white) 50% 20%/5% 20%,
      radial-gradient(circle, transparent 50%, white 52%, white 70%, transparent 72%) 50% 80%/33%
        25%;
    background-repeat: no-repeat;
  }

  .switch .shine {
    transition: all 0.3s cubic-bezier(1, 0, 1, 1);
    opacity: 0.3;
    position: absolute;
    width: 100%;
    height: 100%;
    background: linear-gradient(white, transparent 3%) 50% 50%/97% 97%,
      linear-gradient(
          rgba(255, 255, 255, 0.5),
          transparent 50%,
          transparent 80%,
          rgba(255, 255, 255, 0.5)
        )
        50% 50%/97% 97%;
    background-repeat: no-repeat;
  }

  .switch .shadow {
    transition: all 0.3s cubic-bezier(1, 0, 1, 1);
    opacity: 1;
    position: absolute;
    width: 100%;
    height: 100%;
    background: linear-gradient(transparent 70%, rgba(0, 0, 0, 0.8));
    background-repeat: no-repeat;
  }

  @keyframes flicker {
    0% {
      opacity: 1;
    }

    80% {
      opacity: 0.8;
    }

    100% {
      opacity: 1;
    }
  }

  @keyframes light-off {
    0% {
      opacity: 1;
    }

    80% {
      opacity: 0;
    }
  }
</style>