|
@@ -0,0 +1,338 @@
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import { computed, ref } from 'vue';
|
|
|
|
+import { message, Modal } from 'ant-design-vue';
|
|
|
|
+
|
|
|
|
+import SvgIcon from '@/components/SvgIcon.vue';
|
|
|
|
+import { useViewVisible } from '@/hooks/view-visible';
|
|
|
|
+import { t } from '@/i18n';
|
|
|
|
+
|
|
|
|
+import { DeviceType } from '../device-work-status/device-card';
|
|
|
|
+
|
|
|
|
+import type { SegmentedBaseOption } from 'ant-design-vue/es/segmented/src/segmented';
|
|
|
|
+import type { IconObject } from '@/types';
|
|
|
|
+
|
|
|
|
+const { visible, showView, hideView } = useViewVisible();
|
|
|
|
+const activeKey = ref('manualStartStop');
|
|
|
|
+const activeType = ref([DeviceType.冷水主机, DeviceType.冷却塔, DeviceType.冷冻泵, DeviceType.冷却泵]);
|
|
|
|
+
|
|
|
|
+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[];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const deviceTypeGroups = computed<DeviceTypeGroupItem[]>(() => {
|
|
|
|
+ return [
|
|
|
|
+ {
|
|
|
|
+ name: t('common.chilledWaterUnit'),
|
|
|
|
+ icon: {
|
|
|
|
+ name: 'chiller-unit',
|
|
|
|
+ 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 },
|
|
|
|
+ ],
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: t('common.coolingTower'),
|
|
|
|
+ icon: {
|
|
|
|
+ name: 'cooling-tower',
|
|
|
|
+ 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 },
|
|
|
|
+ ],
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: t('common.chilledWaterPump'),
|
|
|
|
+ icon: {
|
|
|
|
+ name: 'cooling-pump',
|
|
|
|
+ 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 },
|
|
|
|
+ ],
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ name: t('common.coolingPump'),
|
|
|
|
+ icon: {
|
|
|
|
+ name: 'cooling-pump',
|
|
|
|
+ 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 },
|
|
|
|
+ ],
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+type StartStopValue = 'true' | 'false';
|
|
|
|
+
|
|
|
|
+interface StartStopTypeItem extends SegmentedBaseOption {
|
|
|
|
+ value: StartStopValue;
|
|
|
|
+ payload: {
|
|
|
|
+ title: string;
|
|
|
|
+ confirmTip: string;
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const startStopTypes = computed<StartStopTypeItem[]>(() => {
|
|
|
|
+ return [
|
|
|
|
+ {
|
|
|
|
+ value: 'true',
|
|
|
|
+ payload: {
|
|
|
|
+ title: t('common.automatic'),
|
|
|
|
+ confirmTip: t('realTimeMonitor.confirmSwitchToAutoTip'),
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ value: 'false',
|
|
|
|
+ payload: {
|
|
|
|
+ title: t('common.manual'),
|
|
|
|
+ confirmTip: t('realTimeMonitor.confirmSwitchToManualTip'),
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const handleStartStopClick = (option: StartStopTypeItem, device: DeviceItem) => {
|
|
|
|
+ if (option.disabled || option.value === String(device.auto)) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Modal.confirm({
|
|
|
|
+ title: t('realTimeMonitor.confirmSwitchStartStop', {
|
|
|
|
+ name: device.name,
|
|
|
|
+ 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);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+defineExpose({
|
|
|
|
+ showView,
|
|
|
|
+ hideView,
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <AModal
|
|
|
|
+ v-model:open="visible"
|
|
|
|
+ wrap-class-name="hvac-modal device-batch-modal"
|
|
|
|
+ :centered="true"
|
|
|
|
+ :width="920"
|
|
|
|
+ @ok="hideView"
|
|
|
|
+ >
|
|
|
|
+ <template #title>
|
|
|
|
+ <ATabs class="button-tabs-compact device-batch-tabs" v-model:active-key="activeKey" type="card">
|
|
|
|
+ <ATabPane key="manualStartStop" :tab="$t('realTimeMonitor.manualStartStop')" />
|
|
|
|
+ <ATabPane key="enableDisable" :tab="$t('realTimeMonitor.enableDisable')" />
|
|
|
|
+ </ATabs>
|
|
|
|
+ </template>
|
|
|
|
+ <ACollapse
|
|
|
|
+ class="device-batch-collapse"
|
|
|
|
+ v-model:active-key="activeType"
|
|
|
|
+ expand-icon-position="end"
|
|
|
|
+ :bordered="false"
|
|
|
|
+ ghost
|
|
|
|
+ >
|
|
|
|
+ <ACollapsePanel v-for="item in deviceTypeGroups" :key="item.type" :show-arrow="false">
|
|
|
|
+ <template #header>
|
|
|
|
+ <span class="device-batch-item-header">
|
|
|
|
+ <SvgIcon v-bind="item.icon" />
|
|
|
|
+ <span>{{ item.name }}</span>
|
|
|
|
+ </span>
|
|
|
|
+ </template>
|
|
|
|
+ <div class="device-batch-list-item" v-for="device in item.list" :key="device.id">
|
|
|
|
+ <span
|
|
|
|
+ v-show="isStartStop"
|
|
|
|
+ :class="[
|
|
|
|
+ 'status-dot',
|
|
|
|
+ 'device-batch-list-status',
|
|
|
|
+ {
|
|
|
|
+ 'device-batch-status-offline': device.status === 'off',
|
|
|
|
+ },
|
|
|
|
+ ]"
|
|
|
|
+ ></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">
|
|
|
|
+ <template #label="option">
|
|
|
|
+ <span @click="handleStartStopClick(option as StartStopTypeItem, device)">{{ option.payload.title }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </ASegmented>
|
|
|
|
+ </div>
|
|
|
|
+ </ACollapsePanel>
|
|
|
|
+ </ACollapse>
|
|
|
|
+ </AModal>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<style lang="scss">
|
|
|
|
+.device-batch-modal {
|
|
|
|
+ .ant-modal-body {
|
|
|
|
+ max-height: 650px;
|
|
|
|
+ overflow-y: auto;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-tabs {
|
|
|
|
+ .ant-tabs-nav {
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-collapse.ant-collapse {
|
|
|
|
+ > .ant-collapse-item > {
|
|
|
|
+ .ant-collapse-header {
|
|
|
|
+ padding: 16px 24px;
|
|
|
|
+ background-color: #f5f6f7;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ant-collapse-content > .ant-collapse-content-box {
|
|
|
|
+ padding: 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ant-collapse-item + .ant-collapse-item {
|
|
|
|
+ margin-top: 24px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</style>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.device-batch-item-header {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+
|
|
|
|
+ i {
|
|
|
|
+ width: 21px;
|
|
|
|
+ height: 20px;
|
|
|
|
+ margin-right: 16px;
|
|
|
|
+ color: var(--antd-color-primary);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ span {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ line-height: 24px;
|
|
|
|
+ color: #000;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-list-item {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ height: 56px;
|
|
|
|
+ padding-inline: 24px;
|
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
|
+
|
|
|
|
+ .ant-switch {
|
|
|
|
+ background: #f56c6c;
|
|
|
|
+
|
|
|
|
+ &:hover:not(.ant-switch-disabled) {
|
|
|
|
+ background: #ff9797;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ &.ant-switch-checked {
|
|
|
|
+ background: var(--antd-color-primary);
|
|
|
|
+
|
|
|
|
+ &:hover:not(.ant-switch-disabled) {
|
|
|
|
+ background: var(--antd-color-primary-hover);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ :deep(.ant-segmented) {
|
|
|
|
+ padding: 4px;
|
|
|
|
+ color: var(--antd-color-primary);
|
|
|
|
+ background-color: var(--antd-color-primary-opacity-15);
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+
|
|
|
|
+ .ant-segmented-group > div {
|
|
|
|
+ background-color: var(--antd-color-primary);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ant-segmented-item-label {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ padding-inline: 0;
|
|
|
|
+
|
|
|
|
+ span {
|
|
|
|
+ padding: 5px 16px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ line-height: 22px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ant-segmented-item-selected {
|
|
|
|
+ color: #fff;
|
|
|
|
+ background-color: var(--antd-color-primary);
|
|
|
|
+ box-shadow: none;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .ant-segmented-item {
|
|
|
|
+ + .ant-segmented-item {
|
|
|
|
+ margin-left: 4px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ &:hover:not(.ant-segmented-item-selected, .ant-segmented-item-disabled) {
|
|
|
|
+ color: var(--antd-color-primary);
|
|
|
|
+
|
|
|
|
+ &::after {
|
|
|
|
+ background-color: var(--antd-color-primary-opacity-15);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-list-status {
|
|
|
|
+ margin-right: 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-status-offline {
|
|
|
|
+ --status-dot-rgb: 191, 191, 191; // RGB 颜色分量 (灰色)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.device-batch-list-name {
|
|
|
|
+ flex: 1;
|
|
|
|
+ line-height: 24px;
|
|
|
|
+ color: var(--antd-color-text-secondary);
|
|
|
|
+}
|
|
|
|
+</style>
|