index.vue 5.83 KB
<script lang="ts" setup>
  import { ComponentMode, 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 { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  import { useDataFetch } from '../../../hook/socket/useSocket';
  import { useModal } from '/@/components/Modal';
  import PasswordModal from '../component/PasswordModal.vue';
  import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  import { useControlComand } from '../../../hook/useControlCommand';
  import { useI18n } from '/@/hooks/web/useI18n';
  const props = defineProps<{
    config: ComponentPropsConfigType<typeof option>;
  }>();
  const { t } = useI18n();
  const { getScale, getRatio } = useComponentScale(props);

  const currentValue = ref(false);

  const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();

  const getDesign = computed(() => {
    const { option, persetOption } = props.config;
    const { attribute, attributeRename, componentInfo, commandType, deviceProfileId } = option;

    const { fontSize: persetFontSize, password: persetPassword } = persetOption || {};
    const { fontSize, password } = componentInfo || {};

    const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
    return {
      attribute: attributeRename || tsl?.functionName || attribute,
      fontSize: fontSize || persetFontSize || 14,
      password: password || persetPassword,
      commandType,
    };
  });

  const { loading, doControlSendCommand } = useControlComand();

  const handleSendCommand = async () => {
    if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
    const { option } = props.config || {};
    const result = await doControlSendCommand(option, Number(!unref(currentValue)));
    currentValue.value = result ? !unref(currentValue) : unref(currentValue);
  };

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

    handleSendCommand();
  };

  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">
        <label class="sliding-switch" :style="{ transform: `scale(${getRatio})` }">
          <input
            type="checkbox"
            :value="currentValue"
            :checked="currentValue"
            @change="handleChange"
          />
          <span class="slider"></span>
          <span class="on">ON</span>
          <span class="off">OFF</span>
        </label>
        <div
          class="text-center text-gray-700"
          :style="{
            ...getScale,
            fontSize: (getRatio ? getRatio * getDesign.fontSize : getDesign.fontSize) + 'px',
          }"
        >
          {{ getDesign.attribute || t('business.attributeText') }}
        </div>
      </Spin>
    </main>

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

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

    width: 100%;
    height: 100%;
  }

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

  .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>