582 lines
17 KiB
Vue
582 lines
17 KiB
Vue
<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"
|
||
:dtu_remark="dtu_remark"
|
||
:value="value"
|
||
/>
|
||
<manual-page
|
||
ref="manualPage"
|
||
v-else-if="currentMode === false"
|
||
:liveData="liveData"
|
||
:show="show"
|
||
:status="status"
|
||
: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:{},
|
||
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=0,g=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);
|
||
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) {
|
||
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
|
||
}
|
||
// 最新温湿度数据
|
||
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) {
|
||
|
||
}
|
||
},
|
||
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;
|
||
}
|
||
|
||
/* 新增:弹窗样式 */
|
||
.modal-container {
|
||
width: 600rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx 20rpx;
|
||
}
|
||
.modal-title {
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
color: #333;
|
||
}
|
||
.modal-input-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 35rpx;
|
||
font-size: 26rpx;
|
||
margin-left: 20rpx;
|
||
}
|
||
.modal-label {
|
||
width: 160rpx;
|
||
color: #666;
|
||
}
|
||
.modal-current {
|
||
margin-left: 14rpx;
|
||
color: #333;
|
||
}
|
||
.modal-input {
|
||
height: 60rpx;
|
||
border: 1px solid #eee;
|
||
border-radius: 8rpx;
|
||
padding: 0 15rpx;
|
||
font-size: 26rpx;
|
||
width: 80rpx;
|
||
}
|
||
.modal-unit {
|
||
margin-left: 10rpx;
|
||
color: #666;
|
||
}
|
||
.modal-btn-wrap {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 40rpx;
|
||
}
|
||
.modal-btn {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.modal-btn.cancel {
|
||
background: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
.modal-btn.confirm {
|
||
background: #007aff;
|
||
color: #fff;
|
||
}
|
||
.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>
|