498 lines
14 KiB
Vue
498 lines
14 KiB
Vue
<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>
|