FenXiNspBackControl/src/views/appmana/deploy/components/ControlModal.vue

498 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-modal
title="球阀控制"
:width="width"
:body-style="bodystyle"
:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
footer=""
v-model:open="visibleloading"
>
<a-descriptions
title="运行状态"
layout="vertical"
bordered
:labelStyle="{ fontWeight: 'bold', textAlign: 'center' }"
:contentStyle="{ textAlign: 'center' }"
>
<a-descriptions-item :label="item.runName" v-for="item in formState.runList" :key="item.id">
<span style="color: red" v-if="item.value == '0'">停止</span>
<span style="color: green" v-else-if="item.value == '1'">运行</span>
<span style="color: orange" v-else>待同步</span>
</a-descriptions-item>
</a-descriptions>
<a-descriptions title="" layout="vertical" bordered style="margin-top: 20px">
<a-descriptions-item label="控制项">
<a-form :model="formState" :label-col="labelCol" :wrapper-col="wrapperCol">
<!-- <a-form-item label="上报时间">-->
<!-- <a-input v-model:value="formState.dataTime" disabled="true" />-->
<!-- </a-form-item>-->
<div class="card-container">
<a-row :gutter="16" type="flex">
<a-col :span="8" v-for="group in formState.moduleList" :key="group.id" class="card-col">
<a-card :title="group.groupName" :bordered="true">
<!-- <template #extra><a href="#">一键启动</a></template>-->
<a-form-item :label="item.relayName" v-for="item in group.relayList" :key="item.id">
<a-switch
v-model:checked="item.value"
checked-children="打开"
un-checked-children="关闭"
:checked-value="'1'"
:un-checked-value="'0'"
@change="(checked) => switchChange(checked, item)"
/>
</a-form-item>
</a-card>
</a-col>
</a-row>
</div>
</a-form>
</a-descriptions-item>
</a-descriptions>
<!-- 全屏遮罩层 -->
<div v-if="loading" class="fullscreen-mask">
<a-spin tip="加载中..." size="large" :spinning="loading" />
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, reactive, nextTick, defineExpose } from 'vue';
import type { UnwrapRef } from 'vue';
import { getValveStatus, sendDeviceCmd, getRelayList, sendRelayQuery } from '../SurvDeviceDeploy.api';
import { message } from 'ant-design-vue';
import { usePageMqtt } from '/@/components/mqtt/usePageMqtt';
import { useMessage } from '/@/hooks/web/useMessage';
const visibleloading = ref(true);
const loading = ref(false);
const { createMessage, createConfirm } = useMessage();
let timer = null;
// MQTT 配置
const mqttOptions = {
brokerUrl: import.meta.env.VITE_MQTT_BROKER_URL || 'ws://localhost:8083/mqtt',
username: import.meta.env.VITE_MQTT_USERNAME || 'admin',
password: import.meta.env.VITE_MQTT_PASSWORD || 'admin123',
keepalive: 60,
reconnectPeriod: 5000,
};
// 使用页面级 MQTT
const { isConnected, isConnecting, error, connectAndSubscribe, publish, disconnect } = usePageMqtt(mqttOptions);
// 设备数据
const deviceData = reactive({
temperature: 0,
humidity: 0,
status: 'offline',
lastUpdate: 0,
});
// 消息日志
interface LogEntry {
id: number;
topic: string;
message: string;
timestamp: number;
}
const logs = ref<LogEntry[]>([]);
let logId = 0;
// 格式化时间
const formatTime = (timestamp: number) => {
if (!timestamp) return '--:--:--';
return new Date(timestamp).toLocaleTimeString();
};
// 添加日志
const addLog = (topic: string, message: string) => {
logs.value.unshift({
id: logId++,
topic,
message: typeof message === 'object' ? JSON.stringify(message) : message,
timestamp: Date.now(),
});
// 保留最近100条日志
if (logs.value.length > 100) {
logs.value.pop();
}
};
// 处理接收到的消息
const handleMessage = (payload: string, topic: string) => {
// console.log('处理消息:', topic, payload);
try {
const data = JSON.parse(payload);
// 添加日志
addLog(topic, payload);
let initData = data.rw_prot;
if (!initData) {
console.log('initDataC========goParams');
initData = data.params;
}
console.log('initData========', initData);
if (initData) {
let dataJson = initData.r_data;
if (!dataJson) {
console.log('dataJsonC========GoWdata');
dataJson = initData.w_data;
}
console.log('dataJson========', dataJson);
if (dataJson !== undefined) {
// 先构建源数据的 Map
const sourceMap = dataJson.reduce((map, item) => {
map[item.name] = item.value;
return map;
}, {});
console.log('sourceMapCheck========', sourceMap);
// 然后遍历目标数组赋值
// 1.状态部分
formState.runList.forEach((target) => {
const source = sourceMap[target.runKey];
if (source) {
Object.assign(target, {
value: source,
});
}
});
// 2.控制项部分
formState.moduleList.forEach((target) => {
if (target.relayList) {
// 遍历内层数组
target.relayList.forEach((member) => {
const source = sourceMap[member.relayKey];
console.log(member.relayKey + '========wtf========' + source);
if (source) {
Object.assign(member, {
value: source,
checkValue: source,
});
}
console.log(member.relayKey + '========result========' + member.value + ':::' + member.checkValue);
});
}
});
}
console.log('check-----', formState.moduleList);
deviceData.temperature = data.temperature;
}
} catch (e) {
// 非 JSON 格式,直接作为文本处理
addLog(topic, payload);
}
};
// 发送设备控制指令
const sendCommand = async (topic: string, command: string) => {
if (!isConnected.value) {
addLog('system', 'MQTT未连接无法发送指令');
return;
}
// const payload = {
// command,
// timestamp: Date.now(),
// requestId: `${Date.now()}-${Math.random().toString(36).substring(2, 10)}`,
// };
const payload = command;
console.log('sendCommand===', topic, command);
try {
await publish(topic, payload);
addLog(topic, `发送指令: ${command}`);
} catch (err) {
console.error('发送指令失败:', err);
addLog(topic, `指令发送失败: ${command}`);
}
};
const bodystyle = {
height: '1000px',
margin: '20px',
};
interface FormState {
deployCode: string;
delivery1: boolean;
delivery2: boolean;
delivery3: boolean;
dataTime: string;
moduleList: [];
runList: [];
deployInfo: {};
}
const formState: UnwrapRef<FormState> = reactive({
deployCode: '',
delivery1: false,
delivery2: false,
dataTime: '',
moduleList: [],
runList: [],
deployInfo: {},
});
const labelCol = { style: { width: '150px' } };
const wrapperCol = { span: 14 };
const title = ref<string>('');
const width = ref<number>(1000);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
const key = 'updatable';
const openMessage = () => {
message.loading({ content: '发送指令中', key, duration: 7 });
};
// 页面进入时连接 MQTT
onMounted(() => {});
// 页面关闭时断开连接
onUnmounted(() => {
console.log('页面卸载,断开 MQTT...');
disconnect();
});
function switchChange(checked, relay) {
loading.value = true;
// 保存期望的目标状态(用户点击后的状态)
const ops = checked;
timer = setTimeout(() => {
// 超时未收到回执,重置 Switch 状态
if (relay.value == relay.checkValue) {
//两个值一致时,判定为操作成功,否则无回执或者回执与操作不一致
createConfirm({
title: '操作结果',
iconType: 'success',
content: '操作成功',
cancelButtonProps: {
style: { display: 'none' }, // 隐藏取消按钮
},
onOk: () => {
loading.value = false;
},
});
} else {
if (ops == '1') {
relay.value = '0';
} else {
relay.value = '1';
}
createConfirm({
title: '操作结果',
iconType: 'error',
content: '操作失败,设备未及时响应',
cancelButtonProps: {
style: { display: 'none' }, // 隐藏取消按钮
},
onOk: () => {
loading.value = false;
},
});
}
//重新查询所有参数
fetchRelayStatus(formState.deployInfo.deployId);
}, 6000);
// if (checked === true) {
// ops = '1';
// } else if (checked === false) {
// ops = '0';
// }
// let parmas = { relayId: relay.id, ops: ops };
// sendDeviceCmd(parmas).then((res) => {
// if (res.code == 500) {
// //操作失败
// formState[relay.id] = false;
// message.error({ content: res.message, key, duration: 2 });
// }
// });
if ('1' == ops) {
sendCommand(formState.deployInfo.sendTopic, relay.registerCmdOn);
} else if ('0' == ops) {
sendCommand(formState.deployInfo.sendTopic, relay.registerCmdOff);
}
}
function on1SwitchChange(checked) {
openMessage();
if (checked === true) {
sendDeviceCmd({ deployCode: formState.deployCode, os: '01', valveCode: '01' }).then((res) => {
if (res.code == 500) {
//操作失败
formState.delivery1 = false;
message.error({ content: res.message, key, duration: 2 });
}
});
} else if (checked === false) {
sendDeviceCmd({ deployCode: formState.deployCode, os: '00', valveCode: '01' }).then((res) => {
if (res.code == 500) {
//操作失败
formState.delivery1 = true;
message.error({ content: res.message, key, duration: 2 });
}
});
}
}
function on2SwitchChange(checked) {
openMessage();
if (checked === true) {
sendDeviceCmd({ deployCode: formState.deployCode, os: '01', valveCode: '02' }).then((res) => {
if (res.code == 500) {
formState.delivery2 = false;
message.error({ content: res.message, key, duration: 2 });
}
});
} else if (checked === false) {
sendDeviceCmd({ deployCode: formState.deployCode, os: '00', valveCode: '02' }).then((res) => {
if (res.code == 500) {
formState.delivery2 = true;
message.error({ content: res.message, key, duration: 2 });
}
});
}
}
/**
* 控制
*/
function showup(record) {
title.value = '球阀控制';
visible.value = true;
formState.moduleList = [];
formState.runList = [];
formState.dataTime = '';
formState.deployInfo = record;
console.log(record);
console.log('页面加载,连接 MQTT...', formState.deployInfo.receiveTopic);
// 需要订阅的主题
const subscribeTopic = formState.deployInfo.receiveTopic; // 传感器数据主题
// 连接并订阅
connectAndSubscribe(subscribeTopic, handleMessage);
relayData(record.deployId);
}
function relayData(deployIds) {
getRelayList({ deployId: deployIds }).then((res) => {
if (res.code == 200) {
formState.moduleList = res.result.moduleList;
formState.runList = res.result.runStatus;
formState.dataTime = res.result.dataTime;
} else {
}
});
}
function fetchRelayStatus(deployIds) {
getRelayList({ deployId: deployIds }).then((res) => {
if (res.code == 200) {
formState.dataTime = res.result.dataTime;
} else {
}
});
}
// getValveStatus({ deployCode: record.deployCode }).then((res) => {
// if (res.code == 200) {
// formState.dataTime=res.result.dataTime
// formState.deployCode = record.deployCode
// if(res.result.firstValveStatus == "01"){
// formState.delivery1=true
// }else{
// formState.delivery1=false
// }
// if(res.result.secondValveStatus == "01"){
// formState.delivery2=true
// }else{
// formState.delivery2=false
// }
// } else {
// }
// })
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = disableSubmit.value ? '详情' : '编辑';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
disconnect();
}
defineExpose({
showup,
edit,
disableSubmit,
});
</script>
<style>
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
.card-container {
width: 100%;
}
.card-col {
flex: 0 0 33.333333%; /* 每个卡片占1/3宽度 */
max-width: 33.333333%; /* 最大宽度也是1/3 */
margin-bottom: 16px; /* 行间距 */
}
/* 可选:固定卡片高度统一 */
:deep(.ant-card) {
height: 100%;
min-height: 180px;
}
.fullscreen-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
</style>