agri-app/pages/home/control/index.vue

555 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<z-paging ref="paging" refresher-only bg-color="var(--gradualCyanLight)" :show-empty="false" :show-footer="false" @onRefresh="refresh">
<template #refresher="{refresherStatus}">
<!-- 此处的custom-refresh为demo中自定义的组件非z-paging的内置组件请在实际项目中自行创建这里插入什么view下拉刷新就显示什么view -->
<custom-refresher :status="refresherStatus" />
</template>
<view class="card shadow shadow-lg bg-white ">
<uni-section :title="`当前大棚:【${selectedText}】`" :subTitle="imei" titleFontSize="20px" type="line" >
<template v-slot:right>
<view class="switch-row">
<text class="modal-text">手动</text>
<tn-switch
v-model="currentMode"
:size="60"
@change="confirmSwitch"
/>
<text class="modal-text">自动</text>
</view>
</template>
<uni-divider margin="10rpx 0"></uni-divider>
<!-- 把解析好的完整数据传给子组件 -->
<auto-page
ref="autoPage"
v-if="currentMode === true"
:show="show"
:status="status"
:limitTimes="limitTimes"
:dtu_remark="dtu_remark"
:selectedText="selectedText"
:value="value"
:agriId="agriId"
@publicMsg="publishMessage"
@getAgriRemark="getRemarkByImei"
@getAgriLimit="getAgriByImei"
/>
<manual-page
ref="manualPage"
v-else-if="currentMode === false"
:liveData="liveData"
:show="show"
:status="status"
:fontStyle="fontStyle"
:limitTimes="limitTimes"
:dtu_remark="dtu_remark"
:selectedText="selectedText"
:value="value"
:agriId="agriId"
@publicMsg="publishMessage"
@getAgriRemark="getRemarkByImei"
@getAgriLimit="getAgriByImei"
/>
</uni-section>
</view>
</z-paging>
</view>
</template>
<script>
// 优化:抽离魔法值常量
import ManualPage from "./manual.vue"
import AutoPage from "./automatic.vue"
const SENSOR_MAP = {
temp1: "201", temp2: "202", temp3: "203", temp4: "204",
humi1: "101", humi2: "102", humi3: "103", humi4: "104"
};
const MQTT_TOPIC_SUFFIX = { UP: "/+", DOWN: "/control" };
import mqttUtil from '@/utils/mqtt';
import {listAgri} from "../../../api/system/assets/agri";
import {getNewSpecialData} from "../../../api/data/specialData";
import store from "../../../store";
import {getRemarkByImei} from "../../../api/system/assets/remark";
import CustomRefresher from "../../../components/custom-refresher/custom-refresher.vue";
import ZPaging from "../../../uni_modules/z-paging/components/z-paging/z-paging.vue";
import {findDtuDataByInfo} from "../../../api/system/data";
import {getAgriByImei} from "../../../api/system/assets/limit";
export default {
dicts: ['sys_data_map'],
components: {
ZPaging,
CustomRefresher,
ManualPage,
AutoPage,
},
data() {
return {
currentMode:false,
mqttConfig: {
subscribeTopic:'/listener',
},
value: 1,
selectedText: '',
// range: [],
agriId:'',
imei:'',
publishTopic: '/control',
// 优化:声明响应式变量 connected
connected: false,
dtu_remark:{},
fontStyle: '',
liveData: {
temp1: '数据加载中...',
temp2: '数据加载中...',
temp3: '数据加载中...',
temp4: '数据加载中...',
humi1: '数据加载中...',
humi2: '数据加载中...',
humi3: '数据加载中...',
humi4: '数据加载中...',
temp: "正在加载中..."
},
// 卡片状态
show: {
jbk: "暂停",
jbg: "暂停",
jlg: "暂停",
jlk: "暂停",
jm1k: "暂停",
jm1g: "暂停",
jm2k: "暂停",
jm2g: "暂停",
jm3k: "暂停",
jm3g: "暂停"
},
status: {
jbk: 0,
jbg: 0,
jlk: 0,
jlg: 0,
jm1k: 0,
jm1g: 0,
jm2k: 0,
jm2g: 0,
jm3k: 0,
jm3g: 0,
deviceTime:"正在加载中..."
},
// 新增:限位时间配置
limitTimes: {
jbkLimit: 0,
jbgLimit: 0,
jlkLimit: 0,
jlgLimit: 0,
jm1kLimit: 0,
jm1gLimit: 0,
jm2kLimit: 0,
jm2gLimit: 0,
jm3kLimit: 0,
jm3gLimit: 0
},
testMsg:'由于线上为真实数据。任何操作均可影响线上功能,故仅作演示',
};
},
onLoad(option) {
if (option.agriInfo) {
const decodedStr = decodeURIComponent(option.agriInfo);
const agriInfo = JSON.parse(decodedStr); // 反序列化为原对象
this.value = agriInfo.imei;
this.selectedText = agriInfo.agriName;
this.agriId = agriInfo.agriId;
this.change(this.value)
}
// 定义所有互斥的键对:[k键, g键]
const mutexPairs = [
['jbk', 'jbg'],
['jlk', 'jlg'],
['jm1k', 'jm1g'],
['jm2k', 'jm2g'],
['jm3k', 'jm3g']
];
// 遍历处理每一组互斥规则k=1则g=0g=1则k=0
mutexPairs.forEach(([kKey, gKey]) => {
if (this.status[kKey] === 1) this.status[gKey] = 0;
if (this.status[gKey] === 1) this.status[kKey] = 0;
});
},
onShow() {
// this.getAgriList();
// 注册MQTT消息回调接收设备消息
mqttUtil.setOnMessageCallback(this.ackMessage);
// 更新连接状态
this.connected = mqttUtil.getMqttState().isConnected;
},
onUnload() {
// 移除MQTT消息回调避免内存泄漏
mqttUtil.removeOnMessageCallback();
},
methods: {
refresh() {
// this.getAgriList()
this.change(this.imei)
mqttUtil.setOnMessageCallback(this.ackMessage);
if (this.currentMode) {
this.$refs.autoPage.refresh();
}
this.$refs.paging.complete();
},
getNewSpecialData() {
getNewSpecialData().then(response => {
if (response.code === 200 && response.data) {
this.makeSpecialData(response.data,false);
this.liveData.temp = "最后更新时间:"+response.data.time;
this.fontStyle = 'font-size:16px;'
}
})
},
change(e) {
this.imei = e;
var clientId = mqttUtil.getMqttState().clientId;
this.connected = mqttUtil.getMqttState().isConnected;
if ((e === 'A' || e==='B' || e==='C')
&& store.getters && store.getters.name === 'admin'
&& this.currentMode===false) {
if (!this.currentMode) {
this.getNewSpecialData(e);
}
this.mqttConfig.subscribeTopic = `frontend/${clientId}/dtu/862538065276061`;
}
if (e !== 'A' && e!=='B' && e!=='C') {
// 优化使用常量拼接MQTT主题
this.publishTopic = `frontend/${clientId}${MQTT_TOPIC_SUFFIX.DOWN}/${this.imei}`;
this.mqttConfig.subscribeTopic = `frontend/${clientId}/dtu/${this.imei}`;
var queryParams = {
imei: this.imei
}
if (!this.currentMode) {
// 最新温湿度数据
findDtuDataByInfo(queryParams).then(response => {
Object.keys(response.data).forEach(key => {
this.liveData[key] = response.data[key] || '已离线..';
});
this.liveData.temp = "最后更新时间:"+response.data.time;
this.fontStyle = 'font-size:16px;'
})
}
this.getAgriByImei();
// 备注
this.getRemarkByImei();
if (e!=="862538065276939"){
const message = JSON.stringify({jbk: 0,read:true})
this.publishMessage(message)
}
}
this.reset();
this.style="";
},
getRemarkByImei() {
getRemarkByImei({imei:this.imei}).then(response => {
if (response.code===200 && (response.data)) {
this.dtu_remark={...response.data}
}
});
},
getAgriByImei() {
getAgriByImei(this.imei).then(response => {
if (response.code === 200) {
if (!response.data) {
return;
}
this.limitTimes = response.data;
}
})
},
getAgriList() {
listAgri().then(response => {
if (response.code === 200) {
this.range = response.rows.map(item => ({
agriId: item.id,
text: item.agriName,
value: item.imei // 提取并改名
}));
if (store.getters && store.getters.name === 'admin') {
this.range.push(
{
text:"八方南棚",
value:"A"
},
{
text:"九方春棚",
value:"B"
},
{
text:"十二方棚",
value:"C"
}
)
}
}
})
},
testFunction(content) {
uni.showModal({
title: '操作提示:',
content: content,
cancelText: '确定',
success: (res) => {
if (res.confirm) {
}
}
})
},
makeSpecialData(msgData, tag) {
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10);
// 1. 提取B/C的tag对应键名精简重复三元
const B_KEYS = {
true: {temp1:'205',humi1:'105',temp2:'206',humi2:'106',temp3:'207',humi3:'107',temp4:'208',humi4:'108'},
false: {temp1:'temp5',humi1:'humi5',temp2:'temp6',humi2:'humi6',temp3:'temp7',humi3:'humi7',temp4:'temp8',humi4:'humi8'}
};
const C_KEYS = {
true: {temp1:'209',humi1:'109',temp2:'210',humi2:'110',temp3:'211',humi3:'111',temp4:'212',humi4:'112'},
false: {temp1:'temp9',humi1:'humi9',temp2:'temp10',humi2:'humi10',temp3:'temp11',humi3:'humi11',temp4:'temp12',humi4:'humi12'}
};
// 2. 简化传感器键名映射表
const IMEI_SENSOR_MAP = {
A: SENSOR_MAP,
B: B_KEYS[tag], // 直接取tag对应的键名替代三元
C: C_KEYS[tag]
};
// 3. 确定传感器键名(精简变量名)
const isAdmin = store.getters?.name === 'admin';
const sk = (isAdmin && ['A','B','C'].includes(this.imei)) ? IMEI_SENSOR_MAP[this.imei] : IMEI_SENSOR_MAP.A;
// 4. 简化liveData的嵌套三元核心优化
this.liveData = Object.fromEntries(
Object.entries(sk).map(([k, skVal]) => [
k,
(tag
? div10(msgData[skVal])
: (this.imei === 'A' ? msgData[k] : msgData[skVal]))
|| "已离线..."
])
);
this.liveData.temp = "最后更新时间:" + this.getCurrentTime();
},
reset() {
Object.keys(this.show).forEach(key => {
this.show[key] = "暂停";
});
Object.keys(this.status).forEach(key => {
if (key === "deviceTime") {
this.status[key] = '正在加载中...';
return;
}
this.status[key] = 0;
});
Object.keys(this.liveData).forEach(key => {
if (key==="temp") {
this.liveData[key] = '正在加载中...';
return;
}
this.liveData[key] = '数据加载中...';
});
},
// 测试专用
testAuto(type) {
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
},
publishMessage(message) {
if (!this.connected || !this.publishTopic || !message) {
return
}
// 调用全局MQTT工具类发布消息
const publishSuccess = mqttUtil.publishMqtt(this.publishTopic, message);
if (publishSuccess) {
this.addMessage(`【指令已发送】imei: ${this.publishTopic},指令: ${message}`);
} else {
this.addMessage(`发布失败:设备:[${this.publishTopic}]`)
}
},
// 消息回调逻辑完全保留(仅依赖全局工具类转发消息)
ackMessage(topic, payload) {
// 1. 先判断是否是目标订阅主题如frontend/\\w+/control/\\w+"
if ((topic !== this.mqttConfig.subscribeTopic+"/ack")
&& (topic !== this.mqttConfig.subscribeTopic+"/listener")) return;
let msgData = {};
// 优化捕获JSON解析异常
try {
msgData = JSON.parse(payload);
} catch (e) {
console.error("MQTT消息解析失败", e, payload);
return;
}
// 3. 区分“回执”和“其他内容”
if (msgData.prop && "suc" in msgData) {
this.handleCommandAck(msgData);
} else if ("msg" in msgData && "clientId" in msgData) {
if (mqttUtil.getMqttState().clientId === msgData.clientId) {
this.$modal.msg(
`${msgData.msg}`
);
}
} else {
this.handleOtherContent(msgData,payload)
}
},
addMessage(content) {
console.info("提示消息:" + content)
},
// 处理指令回执的函数(核心修改:添加定时器+快照)
handleCommandAck(ackData) {
// 拿到指令字段如jm2k和执行状态suc
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
const commandValue = ackData.prop[commandField]; // 这里是0/1
const isSuccess = ackData.suc; // 这里是true
const type = commandField.substring(0,commandField.length -1);
if (isSuccess) {
// 优化:使用$set确保响应式更新
this.$set(this.status, type, commandValue);
this.$set(this.show, type, commandValue === 0 ? "暂停" : "运行");
console.info(`收到回执,更新功能码成功:{"${type}":${commandValue}}`)
}
// ========== 修改:自动停止指令不弹窗 ==========
if (ackData.clientId && (ackData.clientId===mqttUtil.getMqttState().clientId)) {
console.info("用户提示成功!")
this.$modal[isSuccess ? 'msgSuccess' : 'msgError'](
`设备操作${isSuccess ? "成功" : "失败"}`
);
}
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
},
handleOtherContent(msgData,payload) {
// 业务逻辑:处理传感器数据、设备状态等
// 设备状态展示
if (this.value !== 1) {
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g","jlk","jlg"]
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
if (allKeysNumeric) {
this.status = {...msgData}
Object.keys(msgData).forEach(key => {
const value = msgData[key];
this.show[key] = value === 0 ? '暂停' : '运行';
});
this.status.deviceTime = '最后更新时间:' + this.getCurrentTime();
}
if (this.currentMode) return;
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
this.fontStyle = 'font-size:16px;'
this.makeSpecialData(msgData, true);
}
}
},
/**
* 获取格式化后的当前时间
* @returns {string} 格式为 YYYY-MM-DD HH:mm:ss 的当前时间
*/
getCurrentTime() {
const now = new Date();
// 获取年、月、日(补零确保两位数)
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1
const day = String(now.getDate()).padStart(2, '0');
// 获取时、分、秒(补零确保两位数)
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
// 拼接成标准格式
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
confirmSwitch(e) {
const mode = e;
this.currentMode = !mode
var showTip = mode ? '自动模式':'手动模式';
uni.showModal({
title: '操作提示',
content: `确定将【${this.selectedText}】切换为${showTip}`,
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
// todo 修改大棚模式
this.currentMode = mode
if (!mode) {
this.change(this.imei)
}
}
}
})
}
},
onHide() {
mqttUtil.removeOnMessageCallback();
},
beforeDestroy() {
mqttUtil.removeOnMessageCallback();
},
};
</script>
<style lang="scss" scoped>
@import '@/tuniao-ui/index.scss';
@import "@/colorui/main.css";
@import "@/colorui/icon.css";
/deep/ .z-paging-content-fixed {
padding: 20rpx !important;
/* 可选防止margin塌陷加overflow */
overflow: hidden;
}
/deep/ .uni-section-header__slot-right {
color: green;
}
.icon {
margin-left: 15rpx
}
.uni-stat-tooltip {
width: 300rpx;
}
/deep/ .is-input-border {
width: 340rpx;
}
.card {
border-radius: 20rpx;
overflow: hidden;
}
.switch-row {
display: flex;
align-items: center; /* 垂直居中 */
gap: 8px; /* 元素之间的间距,可根据需要调整 */
}
.modal-text {
/* 确保文字和开关在视觉上对齐 */
line-height: 1;
}
</style>