he wei
2025-04-23 b9bd29a1a81f6f7de479e3cc3fdfe3d85fc660bf
src/views/device/realtime/index.vue
@@ -1,111 +1,300 @@
<script setup name="Realtime">
   import { ref, onMounted, reactive, computed, watchEffect } from "vue";
   import lockRecord from "./lockRecord.vue";
   import useWebSocket from "@/hooks/useWebsocket.js";
import { ref, onMounted, reactive, watch, computed, watchEffect, onActivated } from "vue";
import lockRecord from "./lockRecord.vue";
import useWebSocket from "@/hooks/useWebsocket.js";
import stationList from '@/components/stationList.vue';
import useElement from "@/hooks/useElement.js";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
   import useElement from "@/hooks/useElement.js";
import settings from './settings.vue';
   const { $loading, $message, $confirm } = useElement();
   const { sendData, message: listMessage } = useWebSocket("real");
import { storeToRefs } from "pinia";
import { getStationBaojiInfo } from "@/api/station.js";
   const currentAreaId = ref();
   const tableData = ref([]);
   const pageNum = ref(1);
   const pageSize = ref(10);
   const total = ref(0);
   const offline_num = ref(0);
   const online_num = ref(0);
   const sum = ref(0);
   const open_num = ref(0);
   const close_num = ref(0);
   const unLoad_num = ref(0);
   const recordVisible = ref(false);
   const recordTitle = ref('开启关闭记录');
   const currLockId = ref();
import { useUserStore } from '@/store/user.js';
const userStore = useUserStore();
const { urole, permits } = storeToRefs(userStore);
   function itemClickHandler(item) {
      // console.log(item, '====item', item.data);
      // areaId lockName lockState lockType pageNum pageSize
      currentAreaId.value = item.data.id;
      sendMessage();
import isHasPermit from '@/utils/isHasPermit';
import {
   lockOpen,
   openLockBl,
   closeLockBl,
} from '@/api/lockManager.js';
import svgStation from '@/components/svg/svgStation.vue';
// 是否有控制权限
let isCanControl = isHasPermit("control_permit", permits.value);
const { $loading, $message, $confirm, $confirmPwdDo } = useElement();
const { sendData, message: listMessage } = useWebSocket("real");
const baojiId = ref();
const stationId = ref();
const tableData = ref([]);
const pageNum = ref(1);
const pageSize = ref(10);
const total = ref(0);
const offline_num = ref(0);
const online_num = ref(0);
const sum = ref(0);
const open_num = ref(0);
const close_num = ref(0);
const unLoad_num = ref(0);
const recordVisible = ref(false);
const recordTitle = ref('开启关闭记录');
const currLockId = ref();
const queryFlag = ref(0);
const activeName = ref('topology');
const settingsVisible = ref(false);
function itemClickHandler(item) {
   console.log(item, '====item', item);
   const [_baojiId, _stationId] = item.id.split('-');
   // TODO
   // areaId lockName lockState lockType pageNum pageSize
   baojiId.value = _baojiId;
   stationId.value = _stationId;
   getStationInfo();
   sendMessage();
}
const stationName = ref('');
const locationInfo = ref();
watchEffect(() => {
   let _total = 0;
   if (listMessage.value) {
      let {
         pageInfo,
         offLineNum,
         unLoadNum,
         sumLinf,
         openNum,
         onlineNum,
         closeNum
      } = JSON.parse(listMessage.value)?.data2;
    let control = JSON.parse(listMessage.value)?.data3 || [];
    let all = JSON.parse(listMessage.value)?.data4 || [];
    locationInfo.value = {
      control,
      all
    };
      offline_num.value = offLineNum;
      online_num.value = onlineNum;
      sum.value = sumLinf;
      open_num.value = openNum;
      close_num.value = closeNum;
      unLoad_num.value = unLoadNum;
      _total = pageInfo.total;
      tableData.value = pageInfo.list.map(v => ({
         ...v,
         onlineState: ['离线', '在线'][v.lockOnline],
         state: { 0: '已闭锁', 1: '已开锁' }[v.lockState],
         onlineState: { 0: '离线', 1: '在线' }[v.lockOnline],
         modelStr: v.model == 1 ? '在线模式' : '离线模式',
         blStateStr: v.blState == 0 ? '蓝牙关闭' : '蓝牙开启',
         openTime: v.lockState == 1 ? v.lastUpdateTime : '--',
         closeTime: v.lockState == 0 ? v.lastUpdateTime : '--',
      }));
   }
   watchEffect(() => {
    let _total = 0;
      if (listMessage.value) {
         let {
            pageInfo,
            offLineNum,
            unLoadNum,
            sumLinf,
            openNum,
            onlineNum,
            closeNum
         } = JSON.parse(listMessage.value)?.data2;
   total.value = _total;
});
         offline_num.value = offLineNum;
         online_num.value = onlineNum;
         sum.value = sumLinf;
         open_num.value = openNum;
         close_num.value = closeNum;
         unLoad_num.value = unLoadNum;
      _total = pageInfo.total;
         tableData.value = pageInfo.list.map(v => ({
            ...v,
            onlineState: ['离线', '在线'][v.lockOnline],
            state: { '-1': '未安装', 0: '已闭锁', 1: '已开锁' }[v.lockState],
            openTime: v.lockState == 1 ? v.lastUpdateTime : '--',
            closeTime: v.lockState == 0 ? v.lastUpdateTime : '--',
         }));
function getStationInfo() {
   getStationBaojiInfo({
      baojiId: baojiId.value,
      stationId: stationId.value
   }).then((res) => {
      let { code, data, data2, data3 } = res;
      if (code && data) {
         console.log(data);
         stationName.value = data2.stationName + ' ' + data3.baojiName;
      }
   })
      .catch((err) => {
         console.log(err);
      });
    total.value = _total;
}
function open(scope) {
   console.log('scope', scope, '=============');
   let {
      row
   } = scope;
   $confirmPwdDo(() => {
      let loading = $loading();
      lockOpen(scope.row.lockId).then((res) => {
         let { code, data } = res;
         loading.close();
         if (code && data) {
            $message.success("操作成功");
            sendMessage();
         } else {
            $message.error("操作失败");
         }
      })
         .catch((err) => {
            console.log(err);
            loading.close();
         });
   });
}
function viewRecord(data) {
   console.log(data, '999999999999-------');
   let { row } = data;
   // recordTitle.value = `开启关闭记录 - ${row.areaPath} ${row.lockName}`;
   // currLockId.value = row.lockId;
   // recordVisible.value = true;
   router.push({
      path: '/device/history',
      query: {
         stationId: stationId.value,
         baojiId: baojiId.value,
         lockId: row.lockId,
         flag: Math.random()
      }
   });
   function test() {
}
   }
function sendMessage() {
   let params = {
      stationId: stationId.value,
      baojiId: baojiId.value,
      pageNum: pageNum.value,
      pageSize: pageSize.value,
   };
   sendData(JSON.stringify(params));
}
function handleSizeChange(val) {
   pageSize.value = val;
   sendMessage();
}
   function viewRecord(data) {
      console.log(data);
      let { row } = data;
      recordTitle.value = `开启关闭记录 - ${row.areaPath} ${row.lockName}`;
      currLockId.value = row.lockId;
      recordVisible.value = true;
   }
function handleCurrentChange(val) {
   pageNum.value = val;
   sendMessage();
}
   function sendMessage() {
      let params = {
         areaId: currentAreaId.value,
         pageNum: pageNum.value,
         pageSize: pageSize.value,
      };
      sendData(JSON.stringify(params));
   }
   function handleSizeChange(val) {
      pageSize.value = val;
      sendMessage();
   }
function openBl(scope) {
  $confirmPwdDo(() => {
    let loading = $loading();
    openLockBl(
      scope.row.lockId
    ).then((res) => {
      let { code, data } = res;
      loading.close();
      if (code && data) {
        console.log(data);
        $message.success("操作成功");
        sendMessage();
      } else {
        $message.error("操作失败");
      }
    })
      .catch((err) => {
        loading.close();
        console.log(err);
      });
  });
}
   function handleCurrentChange(val) {
      pageNum.value = val;
      sendMessage();
function closeBl(scope) {
  $confirmPwdDo(() => {
    let loading = $loading();
    closeLockBl(
      scope.row.lockId
    ).then((res) => {
      let { code, data } = res;
      loading.close();
      if (code && data) {
        console.log(data);
        $message.success("操作成功");
        sendMessage();
      } else {
        $message.error("操作失败");
      }
    })
      .catch((err) => {
        loading.close();
        console.log(err);
      });
  });
}
function goHistory() {
   router.push({
      path: '/device/history',
      query: {
         stationId: stationId.value,
         baojiId: baojiId.value,
         flag: Math.random()
      }
   })
}
const tree = ref(null);
function settingsOk() {
  settingsVisible.value = false;
  sendMessage();
}
onActivated(() => {
   if (queryFlag.value == route.query.flag) {
      return;
   }
   queryFlag.value = route.query.flag || 0;
   if (route.query.stationId && route.query.baojiId) {
      baojiId.value = route.query.baojiId;
      stationId.value = route.query.stationId;
      tree.value.setCurrent(baojiId.value + '-' + stationId.value);
   }
   console.log('baojiId, stationId', baojiId.value, stationId.value, '=============real');
})
</script>
<template>
  <div class="page-wrapper">
    <div class="page-header">
      <div class="hdw-card-container">
        <hdw-card title="区域列表" is-full>
          <hdw-tree @item-click="itemClickHandler"></hdw-tree>
        </hdw-card>
        <yc-card title="区域列表" is-full>
          <hdw-tree ref="tree" @item-click="itemClickHandler"></hdw-tree>
        </yc-card>
      </div>
    </div>
    <div class="page-content">
      <hdw-card is-full>
      <yc-card is-full>
        <div class="page-content-wrapper">
          <div class="p-title">
            {{ stationName }}
            <div class="btn-grp">
              <el-tooltip
                class="item"
                effect="dark"
                content="历史数据"
                placement="bottom"
              >
                <div class="btn" @click="goHistory">
                  <svg-icon icon-class="history"></svg-icon>
                </div>
              </el-tooltip>
            </div>
          </div>
          <div class="page-content-tools">
            <div class="item">
              <div class="label">设备总数量</div>
@@ -119,10 +308,10 @@
              <div class="label">离线数量</div>
              <div class="value">{{ offline_num }}</div>
            </div>
            <div class="item">
            <!-- <div class="item">
              <div class="label">未安装数量</div>
              <div class="value">{{ unLoad_num }}</div>
            </div>
            </div> -->
            <div class="item">
              <div class="label">已开锁</div>
              <div class="value">{{ open_num }}</div>
@@ -132,56 +321,121 @@
              <div class="value">{{ close_num }}</div>
            </div>
          </div>
          <div class="page-content-table">
            <div class="pos-rel">
              <div class="pos-abs">
                <el-table stripe :data="tableData" border style="width: 100%; height: 100%">
                  <el-table-column type="index" width="50" />
                  <el-table-column prop="lockId" label="锁具ID" width="180" />
                  <el-table-column prop="lockName" label="锁具名称" width="180" />
                  <el-table-column prop="areaPath" label="关联区域" />
                  <el-table-column prop="onlineState" label="在线状态" width="180" />
                  <el-table-column prop="state" label="状态" width="180" />
                  <el-table-column prop="openTime" label="开启时间" width="180" />
                  <el-table-column prop="closeTime" label="关闭时间" width="180" />
                  <el-table-column align="center" fixed="right" label="操作" width="200">
                    <template #default="scope">
                      <el-button type="primary" size="small" :disabled="scope.row.lockState == -1"
                        @click="viewRecord(scope)">历史开启关闭记录</el-button>
                    </template>
                  </el-table-column>
                </el-table>
          <el-tabs type="border-card" v-model="activeName" class="tabs">
            <el-tab-pane name="topology" label="拓扑图">
              <div class="btn-settings" title="配置" @click="settingsVisible = true"></div>
              <svg-station :rtData="tableData" :locationInfo="locationInfo" class="svg-station"></svg-station>
            </el-tab-pane>
            <el-tab-pane name="list" label="列表">
              <div class="page-content-table">
                <div class="pos-rel">
                  <div class="pos-abs">
                    <el-table
                      stripe
                      :data="tableData"
                      border
                      style="width: 100%; height: 100%"
                    >
                      <el-table-column type="index" fixed="left" width="50" />
                      <el-table-column prop="lockId" label="锁具ID" width="180" />
                      <el-table-column
                        prop="lockName"
                        label="锁具名称"
                        width="180"
                      />
                      <el-table-column
                        prop="onlineState"
                        label="在线状态"
                        width="180"
                      />
                      <el-table-column
                        prop="blStateStr"
                        label="蓝牙状态"
                        width="180"
                      />
                      <el-table-column prop="state" label="状态" width="180" />
                      <el-table-column
                        prop="openTime"
                        label="开启时间"
                        width="180"
                      />
                      <el-table-column
                        prop="closeTime"
                        label="关闭时间"
                        width="180"
                      />
                      <el-table-column
                        align="center"
                        fixed="right"
                        label="操作"
                        width="320"
                      >
                        <template #default="scope">
                          <el-button
                            type="danger"
                            size="small"
                            v-if="isCanControl && scope.row.lockOnline == 1 && scope.row.lockState == 0"
                            @click="open(scope)"
                            >远程开锁</el-button
                          >
                          <el-button
                            type="primary"
                            v-if="isCanControl && scope.row.lockOnline == 1 && scope.row.blState == 0"
                            size="small"
                            @click="openBl(scope)"
                            >开启蓝牙</el-button
                          >
                          <el-button
                            type="primary"
                            v-if="isCanControl &&  scope.row.lockOnline == 1 && scope.row.blState == 1"
                            size="small"
                            @click="closeBl(scope)"
                            >关闭蓝牙</el-button
                          >
                          <el-button
                            type="primary"
                            size="small"
                            :disabled="scope.row.lockState == -1"
                            @click="viewRecord(scope)"
                            >历史开启关闭记录</el-button
                          >
                        </template>
                      </el-table-column>
                    </el-table>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="page-content-page">
            <div class="page-tool"></div>
            <div class="el-page-container">
              <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize"
                :page-sizes="[10,20, 40, 60, 80, 100, 200, 300, 400]" size="small"
                layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
                @current-change="handleCurrentChange" />
            </div>
            <div class="page-tool"></div>
          </div>
              <div class="page-content-page">
                <div class="page-tool"></div>
                <div class="el-page-container">
                  <el-pagination
                    v-model:current-page="pageNum"
                    v-model:page-size="pageSize"
                    :page-sizes="[10,20, 40, 60, 80, 100, 200, 300, 400]"
                    size="small"
                    layout="total, sizes, prev, pager, next, jumper"
                    :total="total"
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange"
                  />
                </div>
                <div class="page-tool"></div>
              </div>
            </el-tab-pane>
          </el-tabs>
        </div>
      </hdw-card>
      </yc-card>
    </div>
    <div class="page-footer"></div>
    <!-- 编辑 新建 -->
    <el-dialog v-model="addEditVisible" :title="addEditTitle" width="500" align-center :close-on-click-modal="false">
      <add-edit v-if="addEditVisible" :isBatch="isBatch" :info="editLock" :isAdd="isAdd" @close="addEditVisible = false"
        @ok="okHandle"></add-edit>
    </el-dialog>
    <!-- 开闭锁记录 -->
    <el-dialog class="dialog-record" v-model="recordVisible" :close-on-click-modal="false"
      style="display: flex; flex-direction: column; padding: 16px 0 0; height: 500px; width: 1100px;">
      <template #header>
        <div class="dialog-title">
          {{ recordTitle }}
        </div>
      </template>
      <lock-record v-if="recordVisible" :lockId="currLockId"></lock-record>
    <!-- 配置机柜 -->
    <el-dialog
      v-model="settingsVisible"
      title="配置"
      width="780"
      align-center
      :close-on-click-modal="false"
    >
      <settings v-if="settingsVisible" :locationInfo="locationInfo" @cancel="settingsVisible = false" @success="settingsOk"></settings>
    </el-dialog>
  </div>
</template>
@@ -198,10 +452,41 @@
  }
}
.p-title {
  background: #0ff;
  color: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  border-radius: 6px;
  margin-bottom: 8px;
  font-weight: 700;
  position: relative;
  .btn-grp {
    position: absolute;
    right: 10px;
    .btn {
      cursor: pointer;
    }
  }
}
.page-content-wrapper {
  display: flex;
  flex-direction: column;
  height: 100%;
  .tabs {
    flex: 1;
    :deep(.el-tab-pane) {
      display: flex;
      flex-direction: column;
      height: 100%;
      position: relative;
    }
  }
  .page-content-tools {
    padding-bottom: 8px;
@@ -231,7 +516,7 @@
}
.hdw-card-container {
  width: 240px;
  width: 320px;
  padding-right: 8px;
  height: 100%;
}
@@ -305,4 +590,39 @@
:deep(.el-dialog__body) {
  flex: 1;
}
</style>
:deep(.el-tabs--border-card>.el-tabs__content) {
  padding: 0;
  position: relative;
}
.svg-station {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.btn-settings {
  position: absolute;
  z-index: 1;
  right: 200px;
  top: 20px;
  cursor: pointer;
  width: 36px;
  height: 36px;
  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1024 1024' %3e%3cpath d='M950.857143 402.285714h-51.748572a403.2 403.2 0 0 0-36.571428-86.308571l36.571428-36.571429a73.142857 73.142857 0 0 0 0-103.314285l-51.017142-51.931429a73.142857 73.142857 0 0 0-103.314286 0l-36.571429 36.571429A393.691429 393.691429 0 0 0 621.714286 125.074286V73.142857a73.142857 73.142857 0 0 0-73.142857-73.142857h-73.142858a73.142857 73.142857 0 0 0-73.142857 73.142857v51.931429a393.691429 393.691429 0 0 0-86.308571 35.657143l-36.571429-36.571429a73.142857 73.142857 0 0 0-103.314285 0L124.16 175.908571a73.142857 73.142857 0 0 0 0 103.314286l36.571429 36.571429a403.2 403.2 0 0 0-36.571429 86.308571H73.142857a73.142857 73.142857 0 0 0-73.142857 73.142857v73.142857a73.142857 73.142857 0 0 0 73.142857 73.142858h51.748572a403.2 403.2 0 0 0 36.571428 86.308571l-36.571428 36.571429a73.142857 73.142857 0 0 0 0 103.314285l51.748571 51.748572a73.142857 73.142857 0 0 0 103.314286 0l36.571428-36.571429A393.691429 393.691429 0 0 0 402.285714 898.925714V950.857143a73.142857 73.142857 0 0 0 73.142857 73.142857h73.142858a73.142857 73.142857 0 0 0 73.142857-73.142857v-51.931429a393.691429 393.691429 0 0 0 86.308571-35.657143l36.571429 36.571429a73.142857 73.142857 0 0 0 103.314285 0l51.748572-51.748571a73.142857 73.142857 0 0 0 0-103.314286l-36.571429-36.571429a403.2 403.2 0 0 0 36.571429-86.308571H950.857143a73.142857 73.142857 0 0 0 73.142857-73.142857V475.428571a73.142857 73.142857 0 0 0-73.142857-73.142857zM617.142857 613.668571a146.285714 146.285714 0 1 1-3.474286-206.811428 146.285714 146.285714 0 0 1 3.474286 206.811428z' fill='%230ff' %3e%3c/path%3e%3c/svg%3e") center center / contain no-repeat;
  transform-origin: 50% 50%;
  &:hover {
    animation: rotate 12s linear infinite;
  }
}
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>