403 lines
11 KiB
Vue
403 lines
11 KiB
Vue
<template>
|
||
<a-modal title="球阀控制" :width="width" :body-style="bodystyle" :visible="visible" @ok="handleOk" @cancel="handleCancel" footer="">
|
||
<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>
|
||
</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 } from '../SurvDeviceDeploy.api';
|
||
import { message } from 'ant-design-vue';
|
||
import { usePageMqtt } from '/@/components/mqtt/usePageMqtt';
|
||
|
||
// 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);
|
||
|
||
if (data.rw_prot !== undefined) {
|
||
if (data.rw_prot.r_data !== undefined) {
|
||
// 先构建源数据的 Map
|
||
const sourceMap = data.rw_prot.r_data.reduce((map, item) => {
|
||
map[item.name] = item.value;
|
||
return map;
|
||
}, {});
|
||
|
||
// 然后遍历目标数组赋值
|
||
// 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];
|
||
if (source) {
|
||
Object.assign(member, {
|
||
value: source,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
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) {
|
||
openMessage();
|
||
let ops = checked;
|
||
// 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);
|
||
getRelayList({ deployId: record.deployId }).then((res) => {
|
||
if (res.code == 200) {
|
||
formState.moduleList = res.result.moduleList;
|
||
formState.runList = res.result.runStatus;
|
||
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;
|
||
}
|
||
</style>
|