index.vue 8.38 KB
<script lang="ts" setup>
  import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  import { option } from './config';
  import { Spin } from 'ant-design-vue';
  import { computed, ref, unref } from 'vue';
  import { useComponentScale } from '../../../hook/useComponentScale';
  import { useSendCommand } from '../../../hook/useSendCommand';
  import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  import { useDataFetch } from '../../../hook/socket/useSocket';
  import { getSendValues } from '../config';
  import PasswordModal from '../component/PasswordModal.vue';
  import { useModal } from '/@/components/Modal';

  const props = defineProps<{
    config: ComponentPropsConfigType<typeof option>;
  }>();

  const { getScale, getRatio } = useComponentScale(props);

  const currentValue = ref(false);

  const getDesign = computed(() => {
    const { option, persetOption } = props.config;
    const {
      attribute,
      attributeRename,
      attributeName,
      commandType,
      extensionDesc,
      codeType,
      deviceCode,
      customCommand,
      componentInfo,
    } = option;
    const { fontSize: persetFontSize, password: persetPassword } = persetOption || {};
    const { fontSize, password } = componentInfo || {};
    return {
      attribute: attributeRename || attributeName || attribute,
      extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
      password: password || persetPassword,
      commandType,
      codeType,
      deviceCode,
      customCommand,
      fontSize: fontSize || persetFontSize || 14,
    };
  });

  const { loading, sendCommand } = useSendCommand();
  const handleChange = async (event: Event) => {
    if (unref(getDesign).password) {
      const target = event.target as HTMLInputElement;
      const value = target.checked;
      target.checked = !value;
      currentValue.value = !value;
      openModal(true, { password: unref(getDesign).password, control: event });
      return;
    }

    const target = event.target as HTMLInputElement;
    const value = target.checked;
    const { option } = props.config || {};

    const { values, isModbusCommand, sendValue } =
      (await getSendValues(option, unref(getDesign), value)) || {};

    const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
    flag ? (currentValue.value = value) : (target.checked = !value);
  };

  const handleSendCommand = async (data) => {
    const { control: event } = data || {};
    const target = event.target as HTMLInputElement;
    const value = !target.checked;
    const { option } = props.config || {};

    const { values, isModbusCommand, sendValue } =
      (await getSendValues(option, unref(getDesign), value)) || {};

    const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
    flag ? (currentValue.value = value) : (target.checked = !value);
  };

  const updateFn: DataFetchUpdateFn = (message, attribute) => {
    const { data = {} } = message;
    const [latest] = data[attribute] || [];
    const [_, value] = latest;
    currentValue.value = isNaN(value as unknown as number) ? false : !!Number(value);
  };

  useDataFetch(props, updateFn);
  const [registerModal, { openModal }] = useModal();
</script>

<template>
  <main class="w-full h-full flex flex-col justify-center">
    <DeviceName :config="config" class="text-center" />
    <main class="w-full h-full flex flex-col justify-center items-center">
      <Spin :spinning="loading" class="w-full h-full">
        <div class="toggle-switch" :style="{ transform: `scale(${getRatio})` }">
          <label class="switch">
            <input type="checkbox" :checked="currentValue" @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-500"
          :style="{
            ...getScale,
            fontSize: (getRatio ? getRatio * getDesign.fontSize : getDesign.fontSize) + 'px',
          }"
          >{{ getDesign.attribute || '属性' }}</div
        >
      </Spin>
    </main>

    <PasswordModal @register="registerModal" @success="handleSendCommand" />
  </main>
</template>

<style scoped>
  :deep(.ant-spin-container) {
    @apply !flex !flex-col justify-evenly items-center;

    width: 100%;
    height: 100%;
  }

  :deep(.ant-spin-nested-loading) {
    width: 100%;
    height: 100%;
  }

  .toggle-switch {
    /* flex: 1 1 auto; */
    max-width: 75px;
    width: 75px;
    max-height: 100px;
    height: 100px;

    /* 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>