Explorar el Código

feat(views): 实时监测页面支持批量操作设备状态

wangcong hace 2 semanas
padre
commit
d4b4c49338

+ 4 - 1
src/api/index.ts

@@ -61,6 +61,7 @@ import type {
   GetListItem,
   GroupingList,
   GroupingListData,
+  GroupModuleBatchItem,
   GroupModuleDevData,
   GroupModuleInfo,
   GroupModuleQuery,
@@ -1096,12 +1097,14 @@ export const updateGroupModuleDevData = async (deviceId: number, deviceParamCode
 };
 
 export const getGroupModuleDevStatus = async (groupId: number) => {
-  await request(apiBiz('/moduleInfo/info/batch/status'), {
+  const data = await request<GroupModuleBatchItem[]>(apiBiz('/moduleInfo/info/batch/status'), {
     method: 'POST',
     body: JSON.stringify({
       groupId,
     }),
   });
+
+  return data;
 };
 
 // AI智能启停和寻优,高级设置

+ 1 - 0
src/i18n/locales/zh.json

@@ -621,6 +621,7 @@
       "waterPumpSwitchDelay": "水泵切换延时(秒)"
     },
     "deviceName": "设备名称",
+    "deviceStartStop": "设备启停",
     "deviceStatus": "设备状态",
     "enableDisable": "启用/禁用",
     "fullAutoConfirmTip": "系统一键投入,全程自动加减载",

+ 16 - 0
src/types/index.ts

@@ -2183,6 +2183,22 @@ export interface GroupModuleDevData {
   }[];
 }
 
+export interface GroupModuleBatchItem {
+  deviceType: number;
+  deviceDataList: GroupBatchDeviceItem[];
+}
+
+export interface GroupBatchDeviceItem {
+  deviceId: number;
+  deviceName: string;
+  deviceType: number;
+  runningStatus: 0 | 1;
+  disableEnableStatus: 0 | 1;
+  manualAutoStatus: 0 | 1;
+  startStopOrder: 0 | 1;
+  localRemoteStatus?: '本地' | '远程' | null;
+}
+
 export interface AIStartStopDeviceItem {
   deviceId: number;
   deviceName: string;

+ 126 - 57
src/views/real-time-monitor/DeviceBatchExe.vue

@@ -1,17 +1,59 @@
 <script setup lang="ts">
-import { computed, ref } from 'vue';
-import { message, Modal } from 'ant-design-vue';
+import { computed, onUnmounted, ref, watch } from 'vue';
+import { Modal } from 'ant-design-vue';
 
 import SvgIcon from '@/components/SvgIcon.vue';
+import { useRequest } from '@/hooks/request';
 import { useViewVisible } from '@/hooks/view-visible';
 import { t } from '@/i18n';
+import { getGroupModuleDevStatus, updateGroupModuleDevData } from '@/api';
 
 import { DeviceType } from '../device-work-status/device-card';
 
+import StartStopTip from './StartStopTip.vue';
+
 import type { SegmentedBaseOption } from 'ant-design-vue/es/segmented/src/segmented';
-import type { IconObject } from '@/types';
+import type { GroupBatchDeviceItem, IconObject } from '@/types';
+
+interface Props {
+  groupId: number;
+}
+
+const props = defineProps<Props>();
 
 const { visible, showView, hideView } = useViewVisible();
+const { isLoading, handleRequest } = useRequest();
+const devicesStatus = ref<Record<number, GroupBatchDeviceItem[] | undefined>>({});
+let devicesStatusTimer: number | undefined;
+
+watch(visible, () => {
+  if (visible.value) {
+    getAllDeviceStatus();
+  } else {
+    devicesStatus.value = {};
+    clearTimeout(devicesStatusTimer);
+  }
+});
+
+onUnmounted(() => {
+  devicesStatus.value = {};
+  clearTimeout(devicesStatusTimer);
+});
+
+const getAllDeviceStatus = () => {
+  clearTimeout(devicesStatusTimer);
+
+  handleRequest(async () => {
+    const data = await getGroupModuleDevStatus(props.groupId);
+
+    data.forEach((item) => {
+      devicesStatus.value[item.deviceType] = item.deviceDataList;
+    });
+  });
+
+  devicesStatusTimer = window.setTimeout(getAllDeviceStatus, 3000);
+};
+
 const activeKey = ref('manualStartStop');
 const activeType = ref([DeviceType.冷水主机, DeviceType.冷却塔, DeviceType.冷冻泵, DeviceType.冷却泵]);
 
@@ -19,20 +61,11 @@ const isStartStop = computed(() => {
   return activeKey.value === 'manualStartStop';
 });
 
-interface DeviceItem {
-  id: number;
-  name: string;
-  type: DeviceType.冷水主机 | DeviceType.冷却塔 | DeviceType.冷冻泵 | DeviceType.冷却泵;
-  status: 'on' | 'off';
-  enable: boolean;
-  auto: boolean;
-}
-
 interface DeviceTypeGroupItem {
   name: string;
   icon: IconObject;
   type: DeviceType.冷水主机 | DeviceType.冷却塔 | DeviceType.冷冻泵 | DeviceType.冷却泵;
-  list: DeviceItem[];
+  list: GroupBatchDeviceItem[];
 }
 
 const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
@@ -44,11 +77,7 @@ const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
         size: 21,
       },
       type: DeviceType.冷水主机,
-      list: [
-        { id: 1, name: '五期-1#冷水主机', type: DeviceType.冷水主机, status: 'off', enable: true, auto: true },
-        { id: 2, name: '五期-2#冷水主机', type: DeviceType.冷水主机, status: 'on', enable: false, auto: false },
-        { id: 3, name: '五期-3#冷水主机', type: DeviceType.冷水主机, status: 'off', enable: true, auto: true },
-      ],
+      list: devicesStatus.value[DeviceType.冷水主机] || [],
     },
     {
       name: t('common.coolingTower'),
@@ -57,11 +86,7 @@ const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
         size: 23,
       },
       type: DeviceType.冷却塔,
-      list: [
-        { id: 4, name: '五期-1#冷却塔', type: DeviceType.冷却塔, status: 'off', enable: true, auto: true },
-        { id: 5, name: '五期-2#冷却塔', type: DeviceType.冷却塔, status: 'off', enable: true, auto: true },
-        { id: 6, name: '五期-3#冷却塔', type: DeviceType.冷却塔, status: 'off', enable: true, auto: true },
-      ],
+      list: devicesStatus.value[DeviceType.冷却塔] || [],
     },
     {
       name: t('common.chilledWaterPump'),
@@ -70,11 +95,7 @@ const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
         size: 20,
       },
       type: DeviceType.冷冻泵,
-      list: [
-        { id: 7, name: '五期-1#冷冻泵', type: DeviceType.冷冻泵, status: 'off', enable: true, auto: true },
-        { id: 8, name: '五期-2#冷冻泵', type: DeviceType.冷冻泵, status: 'off', enable: true, auto: true },
-        { id: 9, name: '五期-3#冷冻泵', type: DeviceType.冷冻泵, status: 'off', enable: true, auto: true },
-      ],
+      list: devicesStatus.value[DeviceType.冷冻泵] || [],
     },
     {
       name: t('common.coolingPump'),
@@ -83,36 +104,30 @@ const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
         size: 20,
       },
       type: DeviceType.冷却泵,
-      list: [
-        { id: 10, name: '五期-1#冷却泵', type: DeviceType.冷却泵, status: 'off', enable: true, auto: true },
-        { id: 11, name: '五期-2#冷却泵', type: DeviceType.冷却泵, status: 'off', enable: true, auto: true },
-        { id: 12, name: '五期-3#冷却泵', type: DeviceType.冷却泵, status: 'off', enable: true, auto: true },
-      ],
+      list: devicesStatus.value[DeviceType.冷却泵] || [],
     },
   ];
 });
 
-type StartStopValue = 'true' | 'false';
-
-interface StartStopTypeItem extends SegmentedBaseOption {
-  value: StartStopValue;
+interface ManualAutoTypeItem extends SegmentedBaseOption {
+  value: GroupBatchDeviceItem['manualAutoStatus'];
   payload: {
     title: string;
     confirmTip: string;
   };
 }
 
-const startStopTypes = computed<StartStopTypeItem[]>(() => {
+const manualAutoTypes = computed<ManualAutoTypeItem[]>(() => {
   return [
     {
-      value: 'true',
+      value: 1,
       payload: {
         title: t('common.automatic'),
         confirmTip: t('realTimeMonitor.confirmSwitchToAutoTip'),
       },
     },
     {
-      value: 'false',
+      value: 0,
       payload: {
         title: t('common.manual'),
         confirmTip: t('realTimeMonitor.confirmSwitchToManualTip'),
@@ -121,29 +136,63 @@ const startStopTypes = computed<StartStopTypeItem[]>(() => {
   ];
 });
 
-const handleStartStopClick = (option: StartStopTypeItem, device: DeviceItem) => {
-  if (option.disabled || option.value === String(device.auto)) {
+const handleManualAutoClick = (option: ManualAutoTypeItem, device: GroupBatchDeviceItem) => {
+  if (option.disabled || option.value === device.manualAutoStatus) {
     return;
   }
 
   Modal.confirm({
     title: t('realTimeMonitor.confirmSwitchStartStop', {
-      name: device.name,
+      name: device.deviceName,
       status: option.payload.title,
     }),
     content: option.payload.confirmTip,
     closable: true,
-    async onOk() {
-      try {
-        device.auto = option.value === 'true';
-      } catch (err) {
-        message.error((err as Error).message);
-        console.error(err);
-      }
+    centered: true,
+    onOk() {
+      updateDeviceData(device.deviceId, 'manualAutoStatus', option.value);
     },
   });
 };
 
+const handleDisableEnableSwitch = (device: GroupBatchDeviceItem) => {
+  const content = device.disableEnableStatus
+    ? t('realTimeMonitor.confirmDeviceDisable')
+    : t('realTimeMonitor.confirmDeviceEnable');
+  const value = device.disableEnableStatus ? 0 : 1;
+
+  Modal.confirm({
+    title: t('realTimeMonitor.deviceStartStop'),
+    content,
+    closable: true,
+    centered: true,
+    onOk() {
+      updateDeviceData(device.deviceId, 'disableEnableStatus', value);
+    },
+  });
+};
+
+const handleStartStopSwitch = (device: GroupBatchDeviceItem) => {
+  const content = device.startStopOrder ? t('common.confirmClose') : t('common.confirmOpen');
+  const value = device.startStopOrder ? 0 : 1;
+
+  Modal.confirm({
+    title: t('realTimeMonitor.manualStartStop'),
+    content: content + device.deviceName,
+    closable: true,
+    centered: true,
+    onOk() {
+      updateDeviceData(device.deviceId, 'startStopOrder', value);
+    },
+  });
+};
+
+const updateDeviceData = (deviceId: number, paramCode: string, value: number) => {
+  handleRequest(async () => {
+    await updateGroupModuleDevData(deviceId, paramCode, value);
+  });
+};
+
 defineExpose({
   showView,
   hideView,
@@ -178,27 +227,41 @@ defineExpose({
             <span>{{ item.name }}</span>
           </span>
         </template>
-        <div class="device-batch-list-item" v-for="device in item.list" :key="device.id">
+        <div class="device-batch-list-item" v-for="device in item.list" :key="device.deviceId">
           <span
             v-show="isStartStop"
             :class="[
               'status-dot',
               'device-batch-list-status',
               {
-                'device-batch-status-offline': device.status === 'off',
+                'device-batch-status-offline': device.runningStatus === 0,
               },
             ]"
           ></span>
-          <div class="device-batch-list-name">{{ device.name }}</div>
-          <ASwitch v-show="!isStartStop" v-model:checked="device.enable" />
-          <ASegmented v-show="isStartStop" :value="String(device.auto)" :options="startStopTypes">
+          <div :class="['device-batch-list-name', { 'flex-1': !isStartStop || device.manualAutoStatus }]">
+            {{ device.deviceName }}
+          </div>
+          <div v-show="isStartStop && !device.manualAutoStatus" class="device-batch-start-stop flex-1">
+            <StartStopTip :device-data="device" @click="handleStartStopSwitch(device)" />
+          </div>
+          <ASwitch
+            v-show="!isStartStop"
+            :checked="device.disableEnableStatus"
+            :checked-value="1"
+            :un-checked-value="0"
+            @click="handleDisableEnableSwitch(device)"
+          />
+          <ASegmented v-show="isStartStop" :value="device.manualAutoStatus" :options="manualAutoTypes">
             <template #label="option">
-              <span @click="handleStartStopClick(option as StartStopTypeItem, device)">{{ option.payload.title }}</span>
+              <span @click="handleManualAutoClick(option as ManualAutoTypeItem, device)">
+                {{ option.payload.title }}
+              </span>
             </template>
           </ASegmented>
         </div>
       </ACollapsePanel>
     </ACollapse>
+    <ASpin v-if="isLoading" class="center-loading" :spinning="true" />
   </AModal>
 </template>
 
@@ -331,8 +394,14 @@ defineExpose({
 }
 
 .device-batch-list-name {
-  flex: 1;
   line-height: 24px;
   color: var(--antd-color-text-secondary);
 }
+
+.device-batch-start-stop {
+  display: flex;
+  justify-content: space-between;
+  margin-right: 24px;
+  margin-left: 16px;
+}
 </style>

+ 116 - 0
src/views/real-time-monitor/StartStopTip.vue

@@ -0,0 +1,116 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+import { Modal } from 'ant-design-vue';
+
+import { t } from '@/i18n';
+
+import type { GroupBatchDeviceItem } from '@/types';
+
+interface Props {
+  deviceData: GroupBatchDeviceItem;
+}
+
+const props = defineProps<Props>();
+
+defineEmits<{
+  click: [];
+}>();
+
+const disableSwitch = computed(() => {
+  const { isLocale, isDisable } = getDeviceStatus();
+  return isLocale || isDisable;
+});
+
+const disableTip = computed(() => {
+  const { isLocale, isDisable } = getDeviceStatus();
+  const tips: string[] = [];
+
+  if (isLocale) {
+    tips.push(t('realTimeMonitor.startStopControlTipLocal'));
+  }
+
+  if (isDisable) {
+    tips.push(t('realTimeMonitor.startStopControlTipDisable'));
+  }
+
+  return tips;
+});
+
+const modalContent = computed(() => {
+  let tipText = '';
+
+  disableTip.value.forEach((item, index) => {
+    tipText += `${index + 1}. ${item}\n`;
+  });
+
+  return tipText;
+});
+
+const getDeviceStatus = () => {
+  const { localRemoteStatus, disableEnableStatus } = props.deviceData;
+
+  return {
+    isLocale: localRemoteStatus === '本地',
+    isDisable: disableEnableStatus === 0,
+  };
+};
+
+const showDisableTipModal = () => {
+  Modal.confirm({
+    title: t('common.inoperableReason'),
+    content: modalContent.value,
+    wrapClassName: 'start-stop-tip-modal',
+    closable: true,
+    centered: true,
+    maskClosable: true,
+    okText: t('common.close'),
+  });
+};
+</script>
+
+<template>
+  <div class="start-stop-tip">
+    <template v-if="disableSwitch">
+      <span>{{ disableTip[0] }}</span>
+      <a @click="showDisableTipModal">更多</a>
+    </template>
+  </div>
+  <ASwitch
+    :checked="deviceData.startStopOrder"
+    :checked-value="1"
+    :un-checked-value="0"
+    :disabled="disableSwitch"
+    @click="$emit('click')"
+  />
+</template>
+
+<style lang="scss">
+.start-stop-tip-modal {
+  .ant-modal-confirm-content {
+    white-space: pre-wrap;
+  }
+
+  .ant-btn-default {
+    display: none;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.start-stop-tip {
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 24px;
+
+  span {
+    margin-right: 12px;
+    color: #e6a23c;
+    opacity: 0.65;
+  }
+
+  a {
+    color: var(--antd-color-primary-opacity-65);
+    text-decoration-line: underline;
+  }
+}
+</style>