Newer
Older
robot_dog_patrol_front / src / views / patrol / manage / index.vue
dutingting on 24 Mar 24 KB bug修复
<!-- 巡检任务 -->
<script setup lang="ts">
import { ElMessage, ElMessageBox, ElTooltip, dayjs } from 'element-plus'
import controlImg from '@/assets/tempImages/control.png'
import dog from '@/assets/tempImages/dog-picture.jpg'
import dogArm from '@/assets/tempImages/dog-arm.jpg'
import sgjc from '@/assets/tempImages/sgjc.jpg'
import tyx from '@/assets/tempImages/tyx.jpg'
import yrg from '@/assets/tempImages/yrg.jpg'
import Amap from '@/components/aMap/index.vue'
import { getDictByCode } from '@/api/system/dict'
import useSocket from '@/store/modules/websocket'
import { getDeviceList, getRouteLog } from '@/api/patrol/manage'
import { controlDevice, delRoute, getRouteByDog, getRouteList } from '@/api/patrol/navigation'
const socket = useSocket()
const mapRef = ref() // 地图组件
const currentDevice: any = ref({
  id: '',
  robotName: '--', // 设备名称
  robotCode: '--', // 设备编号
  robotStatus: '--', // 1,有效,0无效
  robotCell: '--', // 电量
  routeId: '', // 正在执行路线id
  route: '--',
  speed: '--',
  name: '--', // 小区名称
  address: '--', // 小区地址
})
// 点击更换设备
const changeDevice = () => {
  ElMessageBox.confirm(
    '确定更换设备吗?',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    },
  ).then(() => {
    ElMessage.info('敬请期待')
  })
}

// 点击解除绑定
const unbind = () => {
  ElMessageBox.confirm(
    '确定解除绑定吗?',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    },
  ).then(() => {
    ElMessage.info('敬请期待')
  })
}

// -------------------------------------------字典------------------------------------------
const approvalLogTypeMap = ref({}) as any // 是否加急{1: 是}

// 获取字典值
async function getDict() {
  // 是否加急
  const res = await getDictByCode('patrolContent')
  res.data.forEach((item: any) => {
    approvalLogTypeMap.value[`${item.value}`] = item.name
  })
}

// -----------------------------------------------表格------------------------------------------------------
// 表格表头
const columns: any = ref([
  { text: '巡检内容', value: 'patrolTypeName', align: 'center' },
  { text: '巡检情况', value: 'patrolContent', align: 'center' },
  { text: '甲烷浓度(ppm.m)', value: 'situation', align: 'center' },
  { text: '时间', value: 'createTime', align: 'center' },
])
const listQuery = ref({
  routeInfoId: '',
  limit: 10,
  offset: 1,
})
const list: any = ref([]) // 数据列表
const total = ref()
const listLoading = ref(false) // 表格loading

// 巡检情况日志获取
const fetchRouteLog = () => {
  console.log('巡检情况日志获取')
  listQuery.value.routeInfoId = currentDevice.value.routeId
  getRouteLog(listQuery.value).then((res) => {
    list.value = res.data.rows.map((item: any) => {
      const url = `${window.localStorage.getItem('BaseUrl')}/static/${item.patrolPicture}`
      return {
        ...item,
        picture: !item.patrolPicture ? '' : url,
        patrolTypeName: item.patrolType ? approvalLogTypeMap.value[item.patrolType] : item.patrolType,
      }
    })
    total.value = res.data.total
  })
}

// 页数发生变化后的操作,可能是页码变化,可能是每页容量变化,此函数必写
function changePage(val: { size: number; page: number }) {
  if (val && val.size) {
    listQuery.value.limit = val.size
  }
  if (val && val.page) {
    listQuery.value.offset = val.page
  }
  fetchRouteLog()
}

// -------------------------------------------------设备相关-------------------------------------------------
// 根据设备区查询当前巡检路线
const fetchRouteByDog = (id: string) => {
  getRouteByDog({ id }).then((res) => {
    currentDevice.value.routeId = res.data[0].id // 正在执行路线id
    fetchRouteLog()
  })
}

// 获取设备列表
const fetchDeviceList = () => {
  const params = {
    robotName: '',
  }
  getDeviceList(params).then((res) => {
    // 先找到所有在线的狗
    const onLineDogList = res.data.rows.filter((item: { robotStatus: string }) => item.robotStatus === '1') || []
    if (onLineDogList.length) {
      currentDevice.value.id = res.data.rows[0].id
      currentDevice.value.robotName = res.data.rows[0].robotName // 设备名称
      currentDevice.value.robotCode = res.data.rows[0].robotCode // 设备编号
      currentDevice.value.robotStatus = `${res.data.rows[0].robotStatus}` === '1' ? '工作中' : '离线' // 1,有效,0无效
      currentDevice.value.robotCell = `${res.data.rows[0].robotCell}%` // 电量
      // currentDevice.value.routeId = res.data.rows[0].routeId // 正在执行路线id
      fetchRouteByDog(currentDevice.value.id)
    }
    else {
      ElMessage.warning('暂无在线的机器狗')
    }
  })
}

// --------------------------------------------开始巡检-------------------------------------------------------------------------
// 开始巡检、急停、恢复
const control = (type: string) => {
  if (type === 'a/') {
    mapRef.value.createDogMarker()
  }
  if (currentDevice.value.id) {
    const params = {
      command: type,
      robotDogId: currentDevice.value.id,
    }
    controlDevice(params).then(() => {
      ElMessage.success('操作成功')
    })
  }
}
// ----------------------------------------------巡检路线----------------------------------------------------------------
const routeList: any = ref([]) // 巡检路线列表
const routePointsList: any = ref([]) // 巡检路线列表
// 获取路线列表
const fetchRouteList = async () => {
  const params = {
    communityAddress: '', //	小区地址
    communityName: '', //		小区名称
    robotName: '', //		机器人名称
    routeName: '', //		巡航线名称
    limit: 999999,
    offset: 1,
  }
  const res = await getRouteList(params)
  if (res && res.data && res.data.rows && res.data.rows.length) {
    routeList.value = res.data.rows
    currentDevice.value.route = routeList.value[0].id
  }
}

// 监听巡检路线变化
watch(() => currentDevice.value.route, (newValue) => {
  const index = routeList.value.findIndex((item: { id: string }) => item.id === newValue)
  if (index !== -1) {
    if (routeList.value[index].routeInfoDetailList && Array.isArray(routeList.value[index].routeInfoDetailList) && routeList.value[index].routeInfoDetailList.length) {
      routePointsList.value = routeList.value[index].routeInfoDetailList.map((item: any) => {
        return {
          ...item,
          ...routeList.value[index],
        }
      })
      console.log('切换的路线的信息:', routePointsList.value)
    }
    else {
      mapRef.value.removeMarkerLayer()
      console.log('找不到路线信息', routeList.value[index])
    }
  }
}, { immediate: true })
// ---------------------------------------图片-----------------------------------------------------------------------
watch(() => socket.data, (newVal: any) => {
  // 监听到新的巡检情况
  if (newVal && newVal.type === 'AI') { // 报警推送
    // let tempData = {}
    if (currentDevice.value.robotCode === newVal.devcode) { // 找到当前查看的狗
      // =========================查询巡检路线==========================
      // const index = routeList.value.findIndex((item: { id: string }) => item.id === newVal.routeInfoId)
      const url = `${window.localStorage.getItem('BaseUrl')}/static/${newVal.picture}}`
      list.value.shift({
        ...newVal,
        picture: url,
        patrolTypeName: newVal.patrolType ? approvalLogTypeMap.value[newVal.patrolType] : newVal.patrolType,
        createTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), // 时间
      })
      // patrolType === 10011代表燃气泄漏,如果isAlarm为1则需要再地图上打点,并弹框显示信息,
      // 如果isAlarm为0,如果已经打点且
      if (newVal.patrolType === '10011') {
        //
      }
    }
  }
  else if (newVal && newVal.type === 'gps') { // 狗的实时位置
    if (currentDevice.value.robotCode === newVal.devcode) { // 找到当前查看的狗
      mapRef.value.startMoving(null, [newVal.gps.gps_x, newVal.gps.gps_y])
    }
  }
}, {
  deep: true,
  immediate: true,
})
// -------------------------------------------实时视频--------------------------------------
const webTrcServeDog: any = ref(null)
const webTrcServeArm: any = ref(null)
const webTrcServeDogDom: any = ref(null)
const webTrcServeArmDom: any = ref(null)
const dogVideoLoading = ref(false)
const armVideoLoading = ref(false)
const videoDogUrl: any = ref('rtsp://176.139.87.16/axis-media/media.amp') // 狗视频地址
const videoArmUrl: any = ref('rtsp://176.139.87.16/axis-media/media.amp') // 机械臂视频地址
// 播放视频
function playVideo() {
  webTrcServeDog.value.connect(videoDogUrl.value)
  webTrcServeArm.value.connect(videoArmUrl.value)
  webTrcServeDogDom.value = document.getElementById('dog-video-container')
  webTrcServeArmDom.value = document.getElementById('arm-video-container')
  webTrcServeDogDom.value.addEventListener('connecting', handleConnectingDog)
  webTrcServeDogDom.value.addEventListener('connected', handleConnectedDog)
  webTrcServeDogDom.value.addEventListener('disconnecting', handleDisconnectingDog)
  webTrcServeDogDom.value.addEventListener('disconnected', handleDisconnectedDog)
  webTrcServeDogDom.value.addEventListener('error', handleErrorDog)
  webTrcServeArmDom.value.addEventListener('connecting', handleConnectingArm)
  webTrcServeArmDom.value.addEventListener('connected', handleConnectedArm)
  webTrcServeArmDom.value.addEventListener('disconnecting', handleDisconnectingArm)
  webTrcServeArmDom.value.addEventListener('disconnected', handleDisconnectedArm)
  webTrcServeArmDom.value.addEventListener('error', handleErrorArm)
}
// 销毁视频
function destroyVideo() {
  if (webTrcServeDog.value) {
    webTrcServeDog.value.disconnect()
    webTrcServeDog.value = null
    webTrcServeDogDom.value.removeEventListener('connecting', handleConnectingDog)
    webTrcServeDogDom.value.removeEventListener('connected', handleConnectedDog)
    webTrcServeDogDom.value.removeEventListener('disconnecting', handleDisconnectingDog)
    webTrcServeDogDom.value.removeEventListener('disconnected', handleDisconnectedDog)
    webTrcServeDogDom.value.removeEventListener('error', handleErrorDog)
  }
  if (webTrcServeArm.value) {
    webTrcServeArm.value.disconnect()
    webTrcServeArm.value = null
    webTrcServeArmDom.value.removeEventListener('connecting', handleConnectingArm)
    webTrcServeArmDom.value.removeEventListener('connected', handleConnectedArm)
    webTrcServeArmDom.value.removeEventListener('disconnecting', handleDisconnectingArm)
    webTrcServeArmDom.value.removeEventListener('disconnected', handleDisconnectedArm)
    webTrcServeArmDom.value.removeEventListener('error', handleErrorArm)
  }
}

// 定义 loading 状态
const loading = ref(false)

function handleConnectingArm() {
  console.log('机械臂:开始尝试建立连接')
  armVideoLoading.value = true
}

function handleConnectedArm() {
  console.log('机械臂:连接已成功建立')
  armVideoLoading.value = false
}

function handleDisconnectingArm() {
  console.log('机械臂:开始断开连接')
  armVideoLoading.value = true
}

function handleDisconnectedArm() {
  console.log('机械臂:连接已断开')
  armVideoLoading.value = false
}

function handleErrorArm(event: any) {
  console.log('机械臂:播放出错:', event)
  armVideoLoading.value = false
}

function handleErrorDog(event: any) {
  console.log('狗:播放出错:', event)
  armVideoLoading.value = false
}

function handleConnectingDog() {
  console.log('狗:开始尝试建立连接')
  dogVideoLoading.value = true
}

function handleConnectedDog() {
  console.log('狗:连接已成功建立')
  dogVideoLoading.value = false
}

function handleDisconnectingDog() {
  console.log('狗:开始断开连接')
  dogVideoLoading.value = true
}

function handleDisconnectedDog() {
  console.log('狗:连接已断开')
  dogVideoLoading.value = false
}

// --------------------------------------------------------------------------------------------
onMounted(async () => {
  await getDict() // 获取字典
  await fetchRouteList() // 获取路线列表
  fetchDeviceList() // 获取设备列表
  // 实时视频
  setTimeout(() => {
    nextTick(() => {
      webTrcServeDog.value = new WebRtcStreamer('dog-video-container', 'http://127.0.0.1:8000')
      webTrcServeArm.value = new WebRtcStreamer('arm-video-container', 'http://127.0.0.1:8000')
      playVideo()
    })
  })
})

onBeforeUnmount(() => {
  destroyVideo() // 销毁实时视频
})
</script>

<template>
  <app-container>
    <div class="patrol-manage">
      <div class="box box-top">
        <div class="box-top-item">
          <amap ref="mapRef" :patrol-info-list="routePointsList" />
        </div>
        <!-- <img v-if="hoveredPointImage" :src="hoveredPointImage" alt="Point Image" style="width: 600px; height: 500px;position: fixed;top: 100px;right: 10px;"> -->

        <div style="height: 100%; background-color: #dcdfe6; width: 1px;" />
        <div class="box-top-item">
          <div class="box-title">
            <div class="title">
              <span>关联设备</span>
              <img
                style="width: 20px; height: 20px"
                src="../../../assets/tempImages/icon-link.svg"
              >
            </div>
            <div>
              <el-button type="primary" link @click="changeDevice">
                更换设备
              </el-button>
              <el-button type="danger" link @click="unbind">
                解除绑定
              </el-button>
            </div>
          </div>
          <div class="box-main">
            <img
              style="width: 200px; height: 200px"
              src="../../../assets/tempImages/dog.png"
            >
            <div class="box-device">
              <span>
                <span class="title">设备名称: </span>
                <span class="text">{{ currentDevice.robotName }}</span>
              </span>
              <span>
                <span class="title">设备状态: </span>
                <span class="text" :style="{ color: `${currentDevice.robotStatus}` === '工作中' ? '#afcc85' : '#ccc' }">{{ currentDevice.robotStatus }}</span>
              </span>
              <span>
                <span class="title">设备电量: </span>
                <span class="text">{{ currentDevice.robotCell }}</span>
              </span>
              <div style="display: flex;">
                <div class="title">
                  巡检路线:
                </div>
                <!-- <span class="text">{{ currentDevice.route }}</span> -->
                <el-select v-model="currentDevice.route" style="width: 160px;" disabled>
                  <el-option
                    v-for="item in routeList"
                    :key="item.id"
                    :value="item.id"
                    :label="item.routeName"
                  />
                </el-select>
              </div>
              <span>
                <span class="title">当前速度: </span>
                <span class="text">{{ currentDevice.speed }}</span>
              </span>
            </div>
          </div>
          <div class="box-bottom" style="display: flex;justify-content: space-around;">
            <el-button type="primary" @click="control('a/')">
              开始巡检
            </el-button>
          </div>
        </div>
      </div>
      <div class="box-middle">
        <table-container title="巡检情况" style="margin-top: 0px !important;">
          <normal-table
            :data="list"
            :total="total"
            :pagination="true"
            :columns="columns"
            :query="listQuery"
            :height="500"
            :list-loading="listLoading"
            @change="changePage"
          >
            <template #preColumns>
              <el-table-column label="序号" width="55" align="center">
                <template #default="scope">
                  {{ (listQuery.offset - 1) * listQuery.limit + scope.$index + 1 }}
                </template>
              </el-table-column>
            </template>
            <template #columns>
              <el-table-column label="图片" width="160" align="center" fixed="right">
                <template #default="scope">
                  <el-image
                    v-if="scope.row.picture !== ''"
                    style="width: 100px; height: 100px"
                    :src="scope.row.picture"
                    :preview-src-list="[scope.row.picture]"
                  />
                  <span v-else>/</span>
                </template>
              </el-table-column>
              <!-- <el-table-column label="操作" width="160" align="center" fixed="right">
                <template #default="scope">
                  <el-button
                    type="primary"
                    link
                    size="small"
                    class="table-text-button"
                    @click="handleEdit(scope.row)"
                  >
                    详情
                  </el-button>
                </template>
              </el-table-column> -->
            </template>
          </normal-table>
        </table-container>
      </div>
      <div class="box box-bottom">
        <div class="title">
          <span>设备控制</span>
          <div>
            <el-button type="danger" @click="control('s/')">
              急停
            </el-button>
            <el-button type="primary" @click="control('d/')">
              恢复
            </el-button>
          </div>
        </div>
        <!-- <div style="margin-top: 10px;display: flex;justify-content: space-around;">
          <div style="position: relative;">
            <div class="control-title">
              机器狗
            </div>
            <div name="four" style="width: 150px; height: 130px;">
              <el-image style="width: 130px; height: 130px;position: absolute;top: 5px;left: 50px;" :src="controlImg" fit="fill"/>
              <el-image style="width: 200px; height: 200px;position: absolute;top: 5px;left: 200px;" :src="dog" fit="fill" />
              <div>
                <div class="round-btn" style="top: 8px;left: 95px;" @mousedown="controlWithSpeed($event, 'up')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 90px;left: 95px;" @mousedown="controlWithSpeed($event, 'down')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 48px;left: 53px;" @mousedown="controlWithSpeed($event, 'left')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 48px;left: 135px;" @mousedown="controlWithSpeed($event, 'right')" @mouseup="controlWithSpeed($event, 'stop')" />
              </div>
            </div>
          </div>
          <div style="position: relative;">
            <div class="control-title" style="background-color: #a43bd1;">
              机械臂
            </div>
            <div name="four" style="width: 320px; height: 130px;">
              <img style="width: 130px; height: 130px;position: absolute;top: 5px;left: 50px;" :src="controlImg" fit="fill">
              <img style="width: 200px; height: 200px;position: absolute;top: 5px;left: 200px;" :src="dogArm" fit="fill">
              <div>
                <div class="round-btn" style="top: 8px;left: 95px;" @mousedown="controlWithSpeed($event, 'up')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 90px;left: 95px;" @mousedown="controlWithSpeed($event, 'down')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 48px;left: 53px;" @mousedown="controlWithSpeed($event, 'left')" @mouseup="controlWithSpeed($event, 'stop')" />
                <div class="round-btn" style="top: 48px;left: 135px;" @mousedown="controlWithSpeed($event, 'right')" @mouseup="controlWithSpeed($event, 'stop')" />
              </div>
            </div>
          </div>
        </div> -->
        <!-- 实时视频模块 -->
        <div style="display: flex;justify-content: space-around;overflow: auto;">
          <div v-loading="dogVideoLoading">
            <h5 class="inspection-title">巡检精灵</h5>
            <video
              id="dog-video-container"
              autoplay
              width="640"
              height="360"
              controls
              style="background-color: #000;"
            />
          </div>
          <div v-loading="armVideoLoading" style="margin-left: 20px;">
            <h5 class="inspection-title">机械臂</h5>
            <video
              id="arm-video-container"
              autoplay
              width="640"
              height="360"
              controls
              style="background-color: #000;"
            />
          </div>
        </div>
      </div>
    </div>
  </app-container>
</template>

<style scoped lang="scss">
.patrol-manage {
  display: flex;
  flex-direction: column;
  // gap: 10px;
}

.box {
  width: 100%;
  background-color: #fff;
  padding: 10px;
  border-radius: 7px;
}
.box-top {
  display: grid;
  /* 将容器划分为两列,左边占 2 份,右边占 1 份 */
  grid-template-columns: 5fr  0.05fr 1.3fr;
  /* 设置列之间的间距为 20 像素 */
  gap: 20px;

  .box-top-item {
    display: flex;
    flex-direction: column;
    // justify-content: center;
    // align-items: center;
    .box-title {
      font-size: 14px;
      font-weight: 600;
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
      .title {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
    }
    .box-main {
      display:flex;
      flex-direction: column;
      height: 100%;
      // flex-direction: column;
      justify-content: space-around;
      align-items: center;
      .box-device {
        display: flex;
        flex-direction: column;
        line-height: 29px;
        color: rgba(0,0,0,1);
        font-size: 14px;
        text-align: left;
        font-family: SourceHanSansSC-regular;
        .text {
          font-weight: 600;
        }
        .title {
          margin-right: 12px;
        }
      }
    }
  }
}

.box-bottom {
  display: flex;
  flex-direction: column;
  margin-top: 10px;
  .title {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
  .control-title {
    color: white;
    background-color: #ff9f11;
    border-bottom-right-radius: 10px;
    border-top-right-radius: 10px;
    line-height: 30px;
    font-weight: bold;
    text-align: center;
    letter-spacing: 1px;
    font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
    writing-mode: vertical-rl;
    text-orientation: upright;
    width: 30px;
    height: 100px;
  }
  .round-btn {
    position: absolute;
    top: 14px;
    left: 129px;
    width: 42px;
    height: 42px;
    z-index: 111111;
    border-radius: 21px;
    cursor: pointer;
    &:hover {
      background-color: rgb(61 125 254 / 53%);
    }
  }
}

.image-points-container {
  position: relative;
  display: inline-block;
}

canvas {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

.hover-point {
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: rgba(0, 30, 66, 0.5);
  pointer-events: none;
}

.inspection-title {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
    color: #fff;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
    background: linear-gradient(to right, #6a11cb, #2575fc);
    padding: 6px 10px;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    margin: 20px auto;
    width: fit-content;
    transition: all 0.3s ease;
  &:hover {
    transform: scale(1.1);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
  }
}
</style>

<style lang="scss">
.patrol-manage {
  // 单元格样式
  .el-table__cell {
    position: static !important; // 解决el-image 和 el-table冲突层级冲突问题
    z-index: 99999999;
  }

  /* 隐藏默认的悬浮提示内容 */
  .el-tooltip__content {
    display: none;
  }
  .el-tooltip.is-open .el-tooltip__content {
    display: block;
  }
}
</style>