新版Mqtt设备继电器控制界面

This commit is contained in:
zhangyue 2026-08-18 11:22:08 +08:00
parent 3ed475130e
commit fb69119f9f
6 changed files with 1583 additions and 389 deletions

View File

@ -6,7 +6,7 @@ VITE_PUBLIC_PATH = /
# 跨域代理,您可以配置多个 ,请注意,没有换行符
VITE_PROXY = [["/jeecgboot","http://192.168.2.110:9999"],["/upload","http://192.168.2.110:3300/upload"]]
#VITE_PROXY = [["/jeecgboot","https://fxnsp.sxcooh.com/lh-api/"],["/upload","http://192.168.2.111:3300/upload"]]
VITE_PROXY = [["/jeecgboot","https://fxnsp.sxcooh.com/lh-api/"],["/upload","http://192.168.2.111:3300/upload"]]
# 控制台不输出
VITE_DROP_CONSOLE = false
@ -16,7 +16,7 @@ VITE_GLOB_API_URL=/jeecgboot
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://192.168.2.110:9999
#VITE_GLOB_DOMAIN_URL=https://fxnsp.sxcooh.com
VITE_GLOB_DOMAIN_URL=https://fxnsp.sxcooh.com
# 接口前缀
VITE_GLOB_API_URL_PREFIX=

View File

@ -221,5 +221,7 @@ export const getRelayList = (params) => {
* @param isUpdate
*/
export const sendRelayQuery = (params) => {
return defHttp.get({ url: Api.queryRelay, params }, { isTransformResponse: false });
return defHttp.post({ url: Api.queryRelay, params }, { isTransformResponse: false });
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,562 @@
<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 :bordered="true">
<template #title>
<span>{{ group.groupName }}</span>
<a-tag v-if="group.isMutual == 1" color="red" style="margin-left: 8px">组内互斥</a-tag>
</template>
<!-- <template #extra><a href="#">一键启动</a></template>-->
<a-form-item :label="item.relayName" v-for="item in group.relayList" :key="item.id">
<a-switch
v-if="item.relayCate == 1"
v-model:checked="item.value"
checked-children="打开"
un-checked-children="关闭"
:checked-value="'1'"
:un-checked-value="'0'"
@change="(checked) => switchChange(checked, item)"
/>
<a-input v-else-if="item.relayCate == 2" v-model:value="item.value" placeholder="" @blur="inputChange(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];
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(() => {
checkSwitchChange(checked, relay);
}, 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 checkSwitchChange(checked, relay) {
// Switch
if (relay.value == relay.checkValue) {
const currentGroup = formState.moduleList.find((group) => group.id === relay.groupId);
let msg = '操作成功';
if (currentGroup && currentGroup.isMutual == 1 && checked === '1') {
msg = relay.relayName + '已开启,确定后将关闭其他开关';
}
//
createConfirm({
title: '操作结果',
iconType: 'success',
content: msg,
cancelButtonProps: {
style: { display: 'none' }, //
},
onOk: () => {
loading.value = false;
//
if (currentGroup && currentGroup.isMutual == 1 && checked === '1') {
currentGroup.relayList.forEach((other) => {
if (other.id !== relay.id && other.value === '1') {
other.value = '0';
switchChange('0', other);
}
});
}
},
});
} 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);
}
function inputChange(relay) {
loading.value = true;
timer = setTimeout(() => {
// Switch
if (relay.value == relay.checkValue) {
//
createConfirm({
title: '操作结果',
iconType: 'success',
content: '操作成功',
cancelButtonProps: {
style: { display: 'none' }, //
},
onOk: () => {
loading.value = false;
},
});
} else {
relay.value = relay.checkValue;
createConfirm({
title: '操作结果',
iconType: 'error',
content: '操作失败,设备未及时响应',
cancelButtonProps: {
style: { display: 'none' }, //
},
onOk: () => {
loading.value = false;
},
});
}
//
fetchRelayStatus(formState.deployInfo.deployId);
}, 6000);
const finalCmd = relay.registerCmdOn.replace(/#val#/g, relay.value);
sendCommand(formState.deployInfo.sendTopic, finalCmd);
}
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%;
min-height: 180px;
max-height: 500px;
overflow-y: auto;
}
.card-col {
flex: 0 0 33.333333%; /* 每个卡片占1/3宽度 */
max-width: 33.333333%; /* 最大宽度也是1/3 */
margin-bottom: 16px; /* 行间距 */
}
/* 卡片最小高度 */
:deep(.ant-card) {
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>

View File

@ -140,11 +140,11 @@ export const itemFormSchema: FormSchema[] = [
return Promise.reject('数据值不能包含特殊字符!');
}
return new Promise<void>((resolve, reject) => {
console.log('wwwwwww',model)
console.log('wwwwwww', model);
let params = {
deployId: model.deployId,
groupName: value,
id:model.id
id: model.id,
};
itemCheck(params)
.then((res) => {
@ -181,7 +181,17 @@ export const itemFormSchema: FormSchema[] = [
stringToNumber: true,
},
},
{
field: 'isMutual',
label: '组内互斥',
defaultValue: 0,
component: 'JDictSelectTag',
componentProps: {
type: 'radioButton',
dictCode: 'yn',
stringToNumber: true,
},
},
{
label: '备注',
field: 'groupNotes',
@ -193,7 +203,7 @@ export const itemFormSchema: FormSchema[] = [
field: 'sortNo',
required: false,
component: 'InputNumber',
defaultValue: 1
defaultValue: 1,
},
];
@ -218,6 +228,15 @@ export const dictItemColumns: BasicColumn[] = [
return render.renderTag(render.renderDict(text, 'dict_item_status'), color);
},
},
{
title: '组内互斥',
dataIndex: 'isMutual',
width: 80,
customRender: ({ text }) => {
const color = text == '1' ? 'green' : text == '0' ? 'red' : 'gray';
return render.renderTag(render.renderDict(text, 'dict_item_status'), color);
},
},
{
title: '排序',
dataIndex: 'sortNo',

View File

@ -149,7 +149,7 @@ export const itemFormSchema: FormSchema[] = [
let params = {
deployId: model.deployId,
relayKey: value,
id:model.id
id: model.id,
};
relayItemCheck(params)
.then((res) => {
@ -194,9 +194,9 @@ export const itemFormSchema: FormSchema[] = [
componentProps: ({ schema, formModel }) => {
return {
dictCode: 'surv_device_deploy_relaygroup,GROUP_NAME,ID,IS_ENABLE = 1 and DEPLOY_ID=' + formModel.deployId,
dict: 'surv_device_deploy_relaygroup,GROUP_NAME,ID,IS_ENABLE = 1 and DEPLOY_ID='+formModel.deployId
dict: 'surv_device_deploy_relaygroup,GROUP_NAME,ID,IS_ENABLE = 1 and DEPLOY_ID=' + formModel.deployId,
};
}
},
},
// {
// label: '分组编码',
@ -204,7 +204,12 @@ export const itemFormSchema: FormSchema[] = [
// required: false,
// component: 'Input',
// },
{
label: '查询指令',
field: 'registerCmdCheck',
required: false,
component: 'Input',
},
{
label: '开指令',
field: 'registerCmdOn',
@ -224,7 +229,6 @@ export const itemFormSchema: FormSchema[] = [
component: 'Input',
},
{
field: 'isEnable',
label: '是否启用',
@ -253,7 +257,7 @@ export const itemFormSchema: FormSchema[] = [
field: 'sortNo',
required: false,
component: 'InputNumber',
defaultValue :1
defaultValue: 1,
},
];