zhuguifei
2025-09-02 9dfbc038667839631578c12ff748534e5939bc82
pages/tabBar/device.vue
@@ -12,36 +12,38 @@
            <view class="card-box dynamic shadow">
               <view class="title-box margin-bottom-sm">
                  <view  style="width: 100vw;" class="left justify-between">
                  <view style="width: 100vw;" class="left justify-between">
                     <view class="flex align-center">
                        <uni-text class="cuIcon-titles text-blue"></uni-text>
                        <view class="title">总览</view>
                     </view>
                     <view>
                        <text class="text-gray margin-right-lg">2024-07-14</text>
                        <text class="text-gray text-sm">{{curDate}}</text>
                     </view>
                  </view>
               </view>
               <view class="flex flex-direction padding-xs">
               <!-- <view class="flex flex-direction padding-xs">
                  <view class="flex">
                     <view class="flex-sub flex flex-direction text-center">
                        <text class="text-df">设备总数</text>
                        <text class="text-bold text-sl margin-top-xs  text-gray margin-top-sm">20
                        <text class="text-bold text-sl margin-top-xs  text-gray margin-top-sm">{{equCount}}
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                     <view class="flex-sub flex flex-direction text-center">
                        <text class="text-df">开机设备</text>
                        <text class="text-bold text-sl margin-top-xs text-gray margin-top-sm">10
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                     <view class="flex-sub flex flex-direction text-center">
                        <text class="text-df">运行设备</text>
                        <text class="text-bold text-sl margin-top-xs text-cyan margin-top-sm">10
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                     <view class="flex-sub flex flex-direction text-center">
                        <text class="text-df">开机设备</text>
                        <text class="text-bold text-sl margin-top-xs text-gray margin-top-sm">{{onlineCount}}
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                     <view class="flex-sub flex flex-direction text-center">
                        <text class="text-df">运行设备</text>
                        <text class="text-bold text-sl margin-top-xs text-cyan margin-top-sm">{{onlineCount}}
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                  </view>
                  <view class="margin-top">
@@ -51,116 +53,437 @@
                  </view>
               </view> -->
               <view class="flex flex-direction padding-xs">
                  <view class="flex">
                     <view class="flex-sub flex flex-direction">
                        <text class="text-df">在线设备</text>
                        <text class="text-bold text-sl margin-top-xs text-green margin-top-sm">{{onlineCount}}
                           <text class="text-gray text-sm margin-left-xs">台</text></text>
                     </view>
                     <view class="flex-twice flex flex-direction justify-between">
                        <view class="flex-sub flex">
                           <view class="flex flex-direction flex-sub">
                              <text class="text-gray text-xs">设备信息</text>
                              <text class="text-black">设备总数:</text>
                              <text class="margin-lr-xs text-gray text-bold text-xl">{{equCount}}</text>
                              <text class="text-gray text-xs"></text>
                           </view>
                           <view class="flex flex-direction flex-sub">
                              <text class="text-gray text-xs">报警信息</text>
                              <text class="text-black">故障机台:</text>
                              <text class="margin-lr-xs text-red text-bold text-xl">{{faultEquCount}}</text>
                              <text class="text-gray text-xs"></text>
                           </view>
                        </view>
                        <view class="flex-sub flex">
                           <view class="flex flex-direction flex-sub">
                              <text class="text-white text-xs">报警信息</text>
                              <text class="text-black">停用设备:</text>
                              <text class="margin-lr-xs text-orange text-bold text-xl">0</text>
                              <text class="text-gray text-xs"></text>
                           </view>
                           <view class="flex flex-direction flex-sub">
                              <text class="text-white text-xs">机台信息</text>
                              <text class="text-black">告警机台:</text>
                              <text
                                 class="margin-lr-xs text-orange text-bold text-xl">{{alarmEquCount}}</text>
                              <text class="text-gray text-xs"></text>
                           </view>
                        </view>
                     </view>
                  </view>
               </view>
            </view>
         </template>
         <u-skeleton
                 class=" "
               rows="20"
               :loading="loading"
               :title="false"
            >
         <!-- 如果希望其他view跟着页面滚动,可以放在z-paging标签内 -->
         <view class="card-box dynamic shadow" v-for="(item,index) in dataList" :key="index"
            @click="itemClick(item)">
            <view class="title-box">
               <view class="left flex-sub">
                  <uni-text class="cuIcon-titles text-blue"></uni-text>
                  <view class="title text-cut">1#智能干燥机</view>
                  <view class="flex title text-green text-sm">
                     <u-tag size="mini" text="在线" type="success" plain plainFill></u-tag>
                     <u-tag class="margin-left-xs" size="mini" text="停机" type="error" plain plainFill></u-tag>
         <u-skeleton rows="20" :loading="loading" :title="false">
            <!-- 如果希望其他view跟着页面滚动,可以放在z-paging标签内 -->
            <view class="card-item-box dynamic shadow" v-for="(item,index) in dataList" :key="index"
               @click="itemClick(item)">
               <view class="title-box">
                  <view class="left flex-sub">
                     <uni-text class="cuIcon-titles text-blue"></uni-text>
                     <view class="title text-cut">{{$lget(item,'name')}}</view>
                     <view class="flex title text-green text-sm">
                        <u-tag v-if="item.online" size="mini" text="在线" type="success" plain plainFill></u-tag>
                        <u-tag v-else size="mini" text="离线" type="warning" plain plainFill></u-tag>
                        <view style="width: 20rpx;height: 2rpx"></view>
                        <template v-if="$lget(item,'realData.level')!= '--'">
                           <u-tag size="mini" :text="$lget(item,'realData.level')" plain plainFill></u-tag>
                        </template>
                     </view>
                  </view>
                  <view class="right" style="min-width: 240rpx;">
                     <!-- <u-badge :isDot="true" type="success"></u-badge> -->
                     <!-- <view class="title text-gray text-sm">开机时间:</view> -->
                     <view class="title text-gray text-sm">{{$lget(item,'realData.workorder')}}</view>
                  </view>
               </view>
               <view class="right" style="min-width: 240rpx;">
                  <!-- <u-badge :isDot="true" type="success"></u-badge> -->
                  <view class="title text-gray text-sm">开机时间:</view>
                  <view class="title text-gray text-sm">10:00:10</view>
               </view>
            </view>
            <view class="info-box">
               <view class="left flex-sub">
                  <view class="title text-sm">烘干药材:</view>
                  <view class="title text-sm">生地黄</view>
               <view class="info-box">
                  <view class="left flex-sub">
                     <view class="title text-sm">烘干药材:</view>
                     <view class="title text-sm">{{$lget(item,'realData.name')}}</view>
                  </view>
                  <view class="right" style="min-width: 240rpx;">
                     <view class="title text-sm">投料量:</view>
                     <view class="title text-sm text-gray">{{$lget(item,'realData.weight1')}}筐</view>
                  </view>
               </view>
               <view class="right" style="min-width: 240rpx;">
                  <view class="title text-sm">烘干时间:</view>
                  <view class="title text-sm">01:20:00</view>
               </view>
            </view>
            <view class="info-box">
               <view class="left flex-sub">
                  <view class="title text-sm text-cut">报警信息:</view>
                  <view class="flex  title text-sm">
                     <u-tag size="mini" text="温度传感器报警" type="warning" plain plainFill></u-tag>
                     <u-tag size="mini" class="margin-left-xs" text="前门未关" type="warning" plain
                        plainFill></u-tag>
                     <u-tag size="mini" class="margin-left-xs" text="风箱低位报警" type="error" plain
                        plainFill></u-tag>
               <view class="info-box">
                  <view class="left flex-sub">
                     <view class="title text-sm">已用时间:</view>
                     <view class="title text-sm">{{$lget(item,'realData.time3')}}min</view>
                  </view>
                  <view class="right" style="min-width: 240rpx;">
                     <view class="title text-sm">预测剩余:</view>
                     <view class="title text-sm text-gray">{{$lget(item,'realData.ai_total_time')}}min</view>
                  </view>
               </view>
               <view class="info-box">
                  <view class="left flex-sub" style="width: 60%;">
                     <view class="title text-sm text-cut">报警信息:</view>
                     <template v-if="item.errorData">
                        <view class="title text-sm text-red text-cut">{{ (item.errorData.length > 0) ? item.errorData[0]:'' }}</view>
                     </template>
                       <template v-else-if="item.warnData">
                        <view class="title text-sm text-yellow text-cut"> {{(item.warnData.length > 0) ? item.warnData[0]:''}}</view>
                     </template>
                     <view v-if="getErrorCount(item) > 0">
                        <u-tag  size="mini" :text="getErrorCount(item)" type="error" plain></u-tag>
                     </view>
                     <view style="width: 20rpx;"></view>
                     <view v-if="getWarnCount(item) > 0">
                        <u-tag  size="mini" :text="getWarnCount(item)" type="warning" plain></u-tag>
                     </view>
                  </view>
                  <view class="right" style="min-width: 240rpx;">
                     <view class="title text-sm">更新时间:</view>
                     <view class="title text-sm text-gray">{{ item.refreshTime}}</view>
                  </view>
               </view>
            </view>
         </view>
         </u-skeleton>
         </u-skeleton>
      </z-paging>
   </view>
</template>
<script>
   import dayjs from 'dayjs'
   export default {
      data() {
         return {
            loading:true,
         return {
            loading: true,
            // v-model绑定的这个变量不要在分页请求结束中自己赋值!!!
            dataList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            dataList: [],
            curDate: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
            isProcessing: false,
            // key->设备租户+code
            dataMap: new Map(),
            alarmEquCount: 0,
            faultEquCount: 0,
            timer: null
         }
      },
      onShow() {
         this.startTimer()
         this.mqttData()
      },
      onHide() {
         this.stopTimer()
         uni.$off(this.$constant.MQTT_TOPIC_MESSAGE)
      },
      onTabItemTap: function(e) {
         getApp().globalData.selectTab = e.index
      },
      methods: {
         queryList(pageNo, pageSize) {
         queryList(pageNo, pageSize) {
            this.loading = true;
            // 组件加载时会自动触发此方法,因此默认页面加载时会自动触发,无需手动调用
            // 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
            // 模拟请求服务器获取分页数据,请替换成自己的网络请求
            // const params = {
            //    pageNo: pageNo,
            //    pageSize: pageSize,
            // }
            // this.$request.queryList(params).then(res => {
            //    // 将请求的结果数组传递给z-paging
            this.$refs.paging.complete(this.dataList);
            // }).catch(res => {
            //    // 如果请求失败写this.$refs.paging.complete(false);
            //    // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
            //    // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
            //    this.$refs.paging.complete(false);
            // })
            setTimeout(() => {
               this.loading = false
            }, 1000)
            const params = {
               pageNo: pageNo,
               pageSize: pageSize,
            }
            this.$api.queryEquList(params).then((res) => {
               this.$refs.paging.complete(res.result.records);
               this.loading = false
            }).catch(res => {
               this.$refs.paging.complete(false);
               this.loading = false
            })
         },
         // 设备数据和mtqq实时数据合并
         mergeMqttRealData(mqttData) {
            const {
               tenantid,
               machineid
            } = mqttData;
            if (!tenantid || !machineid) return;
            const key = `${tenantid}_${machineid}`;
            const targetItem = this.dataList.find(item =>
               item.tenantId === tenantid && item.code === machineid
            );
            if (targetItem && !targetItem.online) {
               mqttData = {};
            }
            if (targetItem) {
               const updatedItem = {
                  ...targetItem,
                  realData: mqttData
               };
               updatedItem.refreshTime = dayjs().format('HH:mm:ss');
               this.$set(this.dataList, this.dataList.indexOf(targetItem), updatedItem);
            }
         },
         getErrorCount(item){
            let count = 0;
            if(item.errorData){
               count += item.errorData.length
            }
            return count;
         },
         itemClick(item){
            uni.navigateTo({
               url:"/pages/device/control"
            })
         getWarnCount(item){
            let count = 0;
            if(item.warnData){
               count += item.warnData.length
            }
            return count;
         },
         startTimer() {
            this.timer = setInterval(() => {
               //this.queryRealFaultData();
            }, 3000);
         },
         stopTimer() {
            clearInterval(this.timer);
         },
         queryRealFaultData() {
            //发送数据
            const message = {
               req: this.deviceId,
               tenantId: this.tenantId,
               timeStamp: new Date(),
            }
            let opts = {
               topic: this.$constant.MOBILE_REQ_EQU_REAL_FAULT,
               message: JSON.stringify(message),
            }
            this.$mqttTool.publish(opts).then(res => {
            })
         },
         itemClick(item) {
            uni.navigateTo({
               url: "/packageA/pages/device/control?code=" + item.code + "&name=" + item.name + "&clientId=" +
                  item.clientId
            })
         },
         mqttData() {
            uni.$on(this.$constant.MQTT_TOPIC_MESSAGE, (data) => {
               try {
                  // 1. 数据解析和验证
                  const {
                     data: wdata,
                     topic
                  } = this.parseAndValidateMqttData(data);
                  if (!wdata || !topic) return;
                  // 2. 准备主题常量
                  const topics = this.prepareMqttTopics();
                  // 3. 更新刷新时间
                  this.refreshTime = dayjs().format('HH:mm:ss');
                  // 4. 根据主题处理数据
                  switch (topic) {
                     case topics.updateEquStatu:
                        this.handleEquipmentStatusUpdate(wdata);
                        break;
                     case topics.realData:
                        this.mergeMqttRealData(wdata);
                        break;
                     case topics.realFaultTopic:
                     case topics.oneceFaultTopic:
                        this.handleFaultData(wdata);
                        break;
                  }
               } catch (error) {
                  console.error('MQTT数据处理错误:', error);
               }
            });
         },
         // 辅助方法:解析和验证MQTT数据
         parseAndValidateMqttData(data) {
            try {
               const json = JSON.parse(data);
               if (!json?.data || !json?.topic) return {
                  data: null,
                  topic: null
               };
               return {
                  data: json.data,
                  topic: json.topic
               };
            } catch (e) {
               return {
                  data: null,
                  topic: null
               };
            }
         },
         // 辅助方法:准备MQTT主题
         prepareMqttTopics() {
            return {
               updateEquStatu: this.$constant.SERVICE_BROADCAST_TENANT_UPDATE_EQU_STATU
                  .replace('%s', this.tenantId),
               realData: this.$constant.SERVICE_BROADCAST_TENANT_REAL_DATA
                  .replace('%s', this.tenantId),
               realFaultTopic: this.$constant.SERVICE_BROADCAST_TENANT_REAL_FAULT
                  .replace('%s', this.tenantId),
               oneceFaultTopic: this.$constant.SERVICE_ONECE_TENANT_REAL_FAULT.replace('%s', this
                  .deviceId)
            };
         },
         // 辅助方法:处理设备状态更新
         handleEquipmentStatusUpdate(wdata) {
            const {
               connected,
               tenantId,
               code
            } = wdata;
            const index = this.dataList.findIndex(item =>
               (item.tenantId + "") === tenantId && item.code === code
            );
            if (index === -1) return;
            // 更新在线状态
            this.$set(this.dataList[index], 'online', connected);
            // 如果离线,删除实时数据
            if (!connected && this.dataList[index]?.realData) {
               this.$delete(this.dataList[index], 'realData');
            }
         },
         // 辅助方法:处理故障数据
         handleFaultData(wdata) {
            if (!Array.isArray(wdata)) return;
            // 按设备分组故障数据
            const faultMap = wdata.reduce((map, item) => {
               const key = `${item.tenantId}_${item.equCode}`;
               map.get(key)?.push(item) || map.set(key, [item]);
               return map;
            }, new Map());
            // 更新每个设备的故障信息
            faultMap.forEach((faults, key) => {
               const [tenantId, equCode] = key.split("_");
               const targetItem = this.dataList.find(i =>
                  i.tenantId == tenantId && i.code == equCode
               );
               if (targetItem) {
                  const updatedItem = {
                     ...targetItem,
                     errorData: targetItem.online ? faults.filter(f => f.faultType === 1).map(n => n.faultName) : [],
                     warnData: targetItem.online ? faults.filter(f => f.faultType === 2).map(n => n.faultName) : [],
                  };
                  if(targetItem.online){
                     updatedItem.refreshTime = dayjs().format('HH:mm:ss');
                  }
                  this.$set(this.dataList, this.dataList.indexOf(targetItem), updatedItem);
               }
            });
            this.faultEquCount = this.dataList.filter(item => {
               return (
                  item.errorData &&
                  Array.isArray(item.errorData) &&
                  item.errorData.length > 0
               );
            }).length || 0;
            this.alarmEquCount = this.dataList.filter(item => {
               return (
                  item.warnData &&
                  Array.isArray(item.warnData) &&
                  item.warnData.length > 0
               );
            }).length || 0;
         }
      },onReady() {
         setTimeout(() => {
            this.loading = false
         }, 1000)
      },
      onReady() {
      },
      computed: {
         equCount() {
            return this.dataList.length
         },
         onlineCount() {
            const list = this.dataList.filter(item => item.online)
            return list.length
         },
         tenantId() {
            const userinfo = uni.getStorageSync('userinfo');
            const tenantid = userinfo.loginTenantId
            return tenantid + "";
         },
         deviceId() {
            return uni.getStorageSync(this.$constant.DEVICE_ID);
         }
      }
   }
</script>
@@ -179,7 +502,24 @@
      background-color: white;
      border-radius: 20rpx;
      font-family: Helvetica Neue, Helvetica, sans-serif;
   }
   .card-item-box {
      margin: 0rpx 20rpx 10rpx 20rpx;
      padding: 20rpx;
      box-sizing: border-box;
      background-color: white;
      border-radius: 20rpx;
      font-family: Helvetica Neue, Helvetica, sans-serif;
   }
   /* 第二个及之后的 item */
   .card-item-box:not(:first-child) {
      margin: 10rpx 20rpx;
   }
   .margin-left-sm {
      margin-left: 20rpx;
   }