Newer
Older
smartwell_front / src / views / home / operation / alarm / index.vue
liyaguang on 27 Mar 20 KB 首页完成
<!--
  Description: 设备运维-设备报警
  Author: 李亚光
  Date: 2023-06-28
 -->
<script lang="ts" setup name="CurrentAlarmData">
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue'
import MapCom from './components/map.vue'
import { shortcuts } from '@/utils/common'
import { getDictByCode } from '@/api/system/dict'
import { batchRemoveRow, deviceAlarmView, exportDeviceAlarm, getDeviceCurrentListPage } from '@/api/home/operation/alarm'
import { getManufacturerListPage } from '@/api/home/device/product'
import { uniqueMultiArray } from '@/utils/Array'
import { getDeviceTypeListPage } from '@/api/home/device/type'
import { getAlarmTypeListPage } from '@/api/home/rule/alarm'
import { toHumpObject } from '@/utils/String'
import { exportFile } from '@/utils/exportUtils'
import { getDeviceListPage } from '@/api/home/device/device'
import { keepSearchParams } from '@/utils/keepQuery'
import { hexToRgb } from '@/utils/String'
import useUserStore from '@/store/modules/user'
const alarmTypeList = ref<{ id: string; name: string; value: string }[]>([]) // 报警类型
const deviceTypeList = ref<{ id: string; name: string; value: string }[]>([]) // 设备类型
const manufacturerList = ref<{ id: string; name: string; value: string }[]>([]) // 设备类型
const alarmStatusList = ref<{ id: string; name: string; value: string }[]>([]) // 报警状态
const userStore = useUserStore()
// 表格数据
const list1 = ref<any[]>([])
const total = ref(0)
const tableRef = ref()
// 初始展示列
const columns = ref<any>([
  { text: '设备编号', value: 'devCode', align: 'center', isCustom: true, },
  { text: '设备类型', value: 'devTypeName', align: 'center' },
  { text: '报警类型', value: 'alarmType', align: 'center' },
  { text: '报警原因', value: 'alarmContent', align: 'center' },
  { text: '管理单位', value: 'deptName', align: 'center' },
  { text: '状态', value: 'processStatus', align: 'center', width: '110', isCustom: true, },
  { text: '报警时间', value: 'alarmTime', align: 'center', width: '200' },
])
// 最终展示列
const columnsConfig = ref([])
// 修改列
const editColumns = (data: any) => {
  columnsConfig.value = data
}
const loadingTable = ref(true)
//  查询条件
const listQuery = ref({
  limit: 20,
  offset: 1,
  alarmTypeId: '',
  devTypeId: '',
  processStatus: '',
  deptId: '',
  manufacturerId: '',
  position: '',
  begTime: '',
  endTime: '',
  devCode: '',
  overtime: false,
})
// 开始结束时间
const datetimerange = ref()
watch(() => datetimerange.value, (newVal) => {
  listQuery.value.begTime = ''
  listQuery.value.endTime = ''
  if (Array.isArray(newVal)) {
    if (newVal.length) {
      listQuery.value.begTime = `${newVal[0]}`
      listQuery.value.endTime = `${newVal[1]}`
    }
  }
})
// 查询数据
const fetchData = (isLoaidng = false) => {
  loadingTable.value = true
  if (isLoaidng) {
    loadingTable.value = false
  }
  getDeviceCurrentListPage({
    ...listQuery.value,
    overtime: listQuery.value.overtime ? '1' : ''
  }).then((res) => {
    list1.value = res.data.rows.map((item: any) => ({
      ...item,
      // showDeviceTips: false, // 展示设备编号提示
    }))
    total.value = res.data.total
    loadingTable.value = false
    //  查询设备类型和厂商
    // list1.value.forEach((item: any) => {
    //   getDeviceListPage({ offset: 1, limit: 1, devCode: item.devCode }).then(res => {
    //     if (res.data.rows.length) {
    //       item.showDeviceTips = true
    //       item.deviceTips = {
    //         typeName: res.data.rows[0].deviceName || item.devTypeName,
    //         manufactureName: res.data.rows[0].manufactureName
    //       }
    //     }
    //     else {
    //       item.showDeviceTips = false
    //       item.deviceTips = {}
    //     }
    //   })
    // })
  }).catch(() => {
    loadingTable.value = false
  })
}
// 重置查询条件f
const reset = () => {
  datetimerange.value = []
  listQuery.value = {
    limit: 20,
    offset: 1,
    alarmTypeId: '',
    devTypeId: '',
    processStatus: '',
    deptId: '',
    manufacturerId: '',
    position: '',
    begTime: '',
    endTime: '',
    devCode: '',
    overtime: false,
  }
  fetchData()
}
// 页数发生变化后的操作,可能是页码变化,可能是每页容量变化,此函数必写
const changePage = (val: { size: number; page: number }) => {
  if (val && val.size) {
    listQuery.value.limit = val.size
  }
  if (val && val.page) {
    listQuery.value.offset = val.page
  }
  fetchData()
}

onMounted(() => {
  fetchData()
})
// 监听地图宽度
const observer = ref()
const watchMapWidth = () => {
  // 创建一个ResizeObserver实例
  observer.value = new ResizeObserver((entries: any) => {
    for (const entry of entries) {
      // 获取div元素的新高度
      const newWidth = entry.contentRect.width
      // 执行相应的操作,比如更新UI或调用其他函数
      // console.log(`元素新宽度为:${newWidth}`)
      // if (newHeight < 350) {
      //   tableHeight.value = window.innerHeight - 60 - 50 - 10 - 350 - 98 - 10 - 52 - 10 + (350 - newHeight)
      // }
      // 设置地图的宽度
      const map = document.getElementById('map-container')
      if (map) {
        const width = window.innerWidth - newWidth - 180 - 20
        if (width < (window.innerWidth - 180 - 20) * 0.35) {
          return
        }
        map.style.width = `${width}px`
      }
      if (tableFlag.value === 'map') {
        nextTick(() => {
          tableRef.value?.setScrollLeft(10000)
        })
      }
    }
  })
  // 监听目标div元素的宽度变化
  const targetDiv = document.getElementById('container-table') as Element
  observer.value.observe(targetDiv)
}
// 表格标识  地图或普通
const tableFlag = ref('normal')
const tableHeight = ref(0)
const switchMode = (type: string) => {
  if (!list1.value.length) {
    ElMessage.warning('暂无数据')
    return
  }
  tableFlag.value = type
  // tableHeight.value = document.getElementById('container-table')?.clientHeight as number
  tableHeight.value = window.innerHeight - 60 - 50 - 98 - 20
  if (type === 'map') {
    nextTick(() => {
      tableRef.value?.setScrollLeft(10000)
    })
    setTimeout(() => {
      watchMapWidth()
      // console.log('开始监听')
    })
  }
  else {
    const table = document.getElementById('container-table') as HTMLElement
    table.style.width = type === 'normal' ? '100%' : '65%'
  }
}
watch(() => list1.value, () => {
  if (list1.value.length) {
    // tableHeight.value = document.getElementById('container-table')?.clientHeight as number
    tableHeight.value = window.innerHeight - 60 - 50 - 98 - 20
  }
})
window.addEventListener('resize', () => {
  // tableHeight.value = document.getElementById('container-table')?.clientHeight as number
  tableHeight.value = window.innerHeight - 60 - 50 - 98 - 20
})
onBeforeUnmount(() => {
  window.addEventListener('resize', () => { })
  // 销毁ResizeObserver实例
  if (observer.value) {
    observer.value.disconnect()
  }
})
// 查看报警
const mapRef = ref()
const mapData = ref({})
const cacheRow = ref({})
const detail = async (row: any) => {
  // if (row.processStatus === '未读' && !userStore.roleTips.includes('buxiugai')) {
  //   list1.value[list1.value.findIndex((item: any) => item.id === row.id)].processStatus = '待处置'
  //   row.processStatus = '待处置'
  //   // 调用修改状态接口
  // }
  const res = await deviceAlarmView(row.id)
  const data = toHumpObject(res.data)
  // 重新获取列表
  fetchData(true)
  if (!data.latGaode || !data.lngGaode) {
    if(mapRef.value){
      mapRef.value.mapRef.removeMarker()
    }
    ElMessage.warning('该报警缺少坐标信息')
    return
  }
  mapData.value = row
  cacheRow.value = row
  switchMode('map')
  // 绘制点
  const draw = () => {
    mapRef.value.mapRef.removeMarker()
    mapRef.value.mapRef.addMarker({
      position: [data.lngGaode, data.latGaode],
      content: '<div class="spread-circle1"></div>',
      label: '',
      offsetX: -25,
      offsetY: -25,
    })
    mapRef.value.mapRef.map.setFitView()
    mapRef.value.closeInfoDetail()
    // mapRef.value.mapRef.map.setZoom(15)
    // mapRef.value.openInfoDetail({
    //   ...row,
    //   lnglat: [data.lngGaode, data.latGaode],
    // })
  }
  setTimeout(() => {
    if (!data.lngGaode || !data.latGaode) {
      ElMessage.warning('该数据缺少坐标信息')
      return
    }
    // 已读
    if (mapRef.value.completeFlag) {
      draw()
    }
    else {
      const timer = setInterval(() => {
        if(mapRef.value.completeFlag) {
          draw()
          clearInterval(timer)
        }
      }, 500);
      // setTimeout(() => {
      //   draw()
      // }, 1000)
    }
  })
}
// 批量删除
const { proxy } = getCurrentInstance() as any
// 多选
const multipleTable = ref([])
const handleSelectionChange = (val: any) => {
  // console.log(val, '多选数据')
  multipleTable.value = val
}
// 批量删除
const batchDeleteRow = () => {
  if (!multipleTable.value.length) {
    ElMessage.warning('请先选中数据')
    return
  }
  ElMessageBox.confirm(
    '确定要处置选中的数据吗?',
    '确认操作',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    },
  ).then(() => {
    batchRemoveRow(multipleTable.value.map((item: any) => item.id)).then(() => {
      ElMessage.success('操作成功')
      multipleTable.value = []
      tableRef.value.clearMulti()
      fetchData()
    })
  })
}
// 导出列表
const exportList = () => {
  if (!list1.value.length) {
    ElMessage.warning('暂无导出数据')
    return
  }
  const loading = ElLoading.service({
    lock: true,
    background: 'rgba(255, 255, 255, 0.8)',
  })
  exportDeviceAlarm(listQuery.value).then((res) => {
    exportFile(res.data, '当前报警列表(设备).xlsx')
    loading.close()
  }).catch(() => {
    loading.close()
  })
}
// 字典
const fetchDict = () => {
  // 报警类型
  getAlarmTypeListPage({ offset: 1, limit: 99999 }).then((res) => {
    alarmTypeList.value = uniqueMultiArray(res.data.rows.filter((item: any) => item.enabled === '1').map((item: any) => ({
      name: item.alarmType,
      value: item.id,
      id: item.id,
    })), 'name').filter((item: any) => item.name.includes('设备'))
  })
  // 设备类型
  getDeviceTypeListPage({ offset: 1, limit: 99999 }).then((res) => {
    deviceTypeList.value = res.data.rows.map((item: any) => ({
      name: item.typeName,
      value: item.id,
      id: item.id,
    }))
  })
  // 厂商列表
  getManufacturerListPage().then((res) => {
    manufacturerList.value = res.data.rows.map((item: any) => ({
      name: item.name || '',
      id: item.id,
      value: item.id,
    }))
  })
  // 报警状态
  getDictByCode('alarmStatus').then((res) => {
    alarmStatusList.value = res.data
  })
}
fetchDict()

// 跳转设备详情
const $router = useRouter()
const toDeviceDetail = (row: any) => {
  if (!row.devTypeName || (!row.devcode && !row.devCode)) {
    ElMessage.warning('缺少设备关键信息')
    return
  }
  $router.push({
    name: 'DeviceManageDetail',
    params: {
      type: 'detail',
    },
    query: {
      row: JSON.stringify({
        devcode: row.devCode || row.devcode,
        deviceType: row.devTypeName,
        deviceTypeName: row.devTypeName,
        devTypeName: row.devTypeName,
      }),
    },
  })
}
// 状态栏标签样式
const computedStyle = (name: string) => {
  const style = {
    padding: '0 9px',
    'font-size': '12px',
    'border-radius': '4px',
    height: '24px',
    display: 'display: inline-flex',
    'vertical-align': 'middle',
    'justify-content': 'center',
    // 'margin-left': '10px',
    'line-height': '1',
    'align-items': 'center',
    'box-sizing': 'border-box',
  } as { [key: string]: string }
  const handlerStyle = (color: string) => {
    style['color'] = color
    style['border'] = `0.5px solid rgba(${hexToRgb(color)}, 0.5)`
    style['background'] = `rgba(${hexToRgb(color)}, 0.08)`
  }
  if (name.includes('未读')) {
    handlerStyle('#f56c6c')
  }
  else if (name.includes('已读')) {
    handlerStyle('#0D76D4')
  }
  else if (name.includes('待现场确认') || name.includes('待处置')) {
    handlerStyle('#F7C948')
  }
  else if (name.includes('挂起')) {
    handlerStyle('#F58800')
  }
  else if (name.includes('已处置') || name.includes('已确认')) {
    handlerStyle('#67c23a')
  }
  return style
}
// 双击列表行数据
const rowClick = (data: any) => {
  detail(data)
}
const cache = ref('')
onBeforeRouteLeave((to: any) => {
  keepSearchParams(to.path, 'CurrentAlarmData')
  cache.value = tableFlag.value
  // 销毁ResizeObserver实例
  if (observer.value) {
    observer.value.disconnect()
  }
})
onActivated(() => {
  // 从编辑或者新增页面回来需要重新获取列表数据
  // $router.options.history.state.back 上一次路由地址
  if (!($router.options.history.state.back as string || '').includes('detail')) {
    fetchData()
  }
  if (cache.value === 'map') {
    // console.log('重置地图')
    tableFlag.value = 'normal'
    setTimeout(() => {
      switchMode('map')
      if (cacheRow.value.id) {
        detail(cacheRow.value)
      }
    })
  }
})
</script>

<template>
  <!-- 布局 -->
  <app-container>
    <!-- 筛选条件 -->
    <search-area :need-clear="true" @search="fetchData" @clear="reset">
      <search-item>
        <el-select v-model="listQuery.alarmTypeId" placeholder="报警类型" clearable class="select" style="width: 192px;">
          <el-option v-for="item in alarmTypeList" :key="item.id" :value="item.value" :label="item.name" />
        </el-select>
      </search-item>
      <search-item>
        <el-select v-model="listQuery.devTypeId" placeholder="设备类型" clearable class="select" style="width: 192px;">
          <el-option v-for="item in deviceTypeList" :key="item.id" :value="item.value" :label="item.name" />
        </el-select>
      </search-item>
      <search-item>
        <el-select v-model="listQuery.processStatus" placeholder="报警状态" clearable class="select" style="width: 192px;">
          <el-option v-for="item in alarmStatusList" :key="item.id" :value="item.value" :label="item.name" />
        </el-select>
      </search-item>
      <search-item>
        <dept-select v-model="listQuery.deptId" placeholder="管理单位" :clearable="true" class="select"
          style="width: 192px;" />
      </search-item>
      <search-item>
        <el-select v-model="listQuery.manufacturerId" placeholder="厂商" clearable filterable class="select"
          style="width: 192px;">
          <el-option v-for="item in manufacturerList" :key="item.id" :label="item.name" :value="item.value" />
        </el-select>
      </search-item>
      <search-item>
        <el-input v-model.trim="listQuery.position" placeholder="位置" clearable />
      </search-item>
      <search-item>
        <el-input v-model.trim="listQuery.devCode" placeholder="设备编号" clearable />
      </search-item>
      <search-item>
        <el-date-picker v-model="datetimerange" type="datetimerange" format="YYYY-MM-DD HH:mm:ss" :shortcuts="shortcuts"
          value-format="YYYY-MM-DD HH:mm:ss" range-separator="至" start-placeholder="报警开始时间" end-placeholder="报警结束时间"
          clearable />
      </search-item>
      <search-item>
        <el-checkbox v-model="listQuery.overtime" label="48小时未处置" />
      </search-item>
    </search-area>
    <div style="width: 100%;display: flex;">
      <!-- 表头标题 -->
      <div id="container-table" v-drag-width="320" :class="tableFlag === 'normal' ? 'table' : 'table-map'"
        class="container-table">
        <table-container :is-config="true" config-title="device-current-alarm" :columns="columns"
          :config-columns="columnsConfig" :edit="editColumns">
          <template #btns-right>
            <!-- 操作 -->
            <div>
              <el-button v-if="proxy.hasPerm('/alarm/device/delete')" type="primary" @click="batchDeleteRow">
                批量处置
              </el-button>
              <el-button v-if="proxy.hasPerm('/alarm/device/export')" type="primary" @click="exportList">
                导出
              </el-button>
            </div>
          </template>
          <!-- 查询结果Table显示 -->
          <normal-table ref="tableRef" :data="list1" :total="total" :columns="columnsConfig" :query="listQuery"
            :list-loading="loadingTable" :height="tableHeight - 50 - 70"
            :is-multi="proxy.hasPerm('/alarm/device/delete')"
            :is-showmulti-select="proxy.hasPerm('/alarm/device/delete')" @change="changePage"
            @multiSelect="handleSelectionChange" @rowDbClick="rowClick">
            <template #preColumns>
              <el-table-column label="序号" width="80" align="center">
                <template #default="scope">
                  {{ (listQuery.offset - 1) * listQuery.limit + scope.$index + 1 }}
                </template>
              </el-table-column>
            </template>
            <template #isCustom="{ scope, column }">
              <!-- 设备编号 -->
              <span v-if="column.text === '设备编号'" class="pointer link" @click="toDeviceDetail(scope.row)">
                <el-tooltip v-if="scope.row.devTypeName" class="box-item" effect="dark"
                  :content="`${scope.row.devTypeName}(${scope.row.manufactureName})`"
                  placement="top">
                  {{ scope.row[column.value] }}
                </el-tooltip>
                <template v-else>
                  {{ scope.row[column.value] }}
                </template>
              </span>
              <!-- 状态 -->
              <span v-if="column.text === '状态'" :style="computedStyle(scope.row[column.value])">
                {{ scope.row[column.value] }}
              </span>
            </template>
            <template #columns>
              <el-table-column v-if="proxy.hasPerm('/alarm/device/detail')" label="操作" align="center" width="80">
                <template #default="scope">
                  <el-button type="primary" link size="small" @click="detail(scope.row)">
                    查看
                  </el-button>
                </template>
              </el-table-column>
            </template>
          </normal-table>
          <!-- 打开关闭地图模式 -->
          <div v-if="tableFlag !== 'normal'" class="mapMode" :style="`height: ${tableHeight}px`">
            <!-- 打开 -->
            <div v-if="tableFlag === 'normal'" class="open" @click="switchMode('map')">
              <el-icon :size="30">
                <arrow-left-bold :size="30" />
              </el-icon>
            </div>
            <div v-if="tableFlag === 'map'" class="open" @click="switchMode('normal')">
              <el-icon :size="30">
                <arrow-right-bold :size="30" />
              </el-icon>
            </div>
          </div>
        </table-container>
      </div>
      <!-- 地图 -->
      <div v-if="tableFlag === 'map'" :style="`height: ${tableHeight}px`" class="map" id="map-container">
        <map-com ref="mapRef" :height="tableHeight - 10" :data="mapData" @refresh="fetchData(true)" />
      </div>
    </div>
  </app-container>
</template>

<style lang="scss" scoped>
.pointer {
  &:hover {
    cursor: pointer;
  }
}

.link {
  color: #0d76d4 !important;

  &:hover {
    text-decoration: underline !important;
  }
}

.table {
  width: 100%;
}

.table-map {
  width: 65%;
}

.map {
  width: 35%;
  // background-color: antiquewhite;
}

.container-table {
  position: relative;

  .mapMode {
    // background-color: aqua;
    width: 1.5%;
    //  height: 100vh;
    position: absolute;
    right: -6px;
    top: 50%;
    z-index: 9;
    transform: translateY(-50%);

    &:hover {

      .open,
      .close {
        display: block;
      }
    }

    .open {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      display: none;

      &:hover {
        cursor: pointer;
      }
    }
  }
}

.select {
  width: 100%;
}
</style>

<style>
.select {
  width: 192px !important;
}

.spread-circle1 {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: url("@/assets/images/red-spread.svg") no-repeat center center / cover;
}
</style>