You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
584 lines
17 KiB
584 lines
17 KiB
<template>
|
|
<div class="app-container">
|
|
<div class="container-main" style="justify-content: flex-start;">
|
|
<div class="elect-cont-left">
|
|
<el-scrollbar style="height: calc(100vh - 198px);">
|
|
<el-tree
|
|
ref="tree"
|
|
:data="warehouseTreeData"
|
|
:props="defaultProps"
|
|
node-key="id"
|
|
default-expand-all
|
|
:default-checked-keys="defaultCheckedKeys"
|
|
:expand-on-click-node="false"
|
|
:highlight-current="true"
|
|
@node-click="handleNodeClick"
|
|
/>
|
|
</el-scrollbar>
|
|
</div>
|
|
|
|
<div class="elect-cont-right" style="background-color: transparent; padding: 0 !important;">
|
|
<div class="container-right" style="height: calc(100vh - 158px) !important;background-color: transparent !important;">
|
|
<span class="right-top-line" />
|
|
<span class="left-bottom-line" />
|
|
<div class="monitor-list" style="height: 100%; overflow-y: auto;">
|
|
<el-card
|
|
v-for="(item, index) in warehouseCardList"
|
|
:key="index"
|
|
class="list-card"
|
|
:class="{ 'warning-active': item.isWarningActive }"
|
|
>
|
|
<div slot="header" class="store-info">
|
|
<img src="@/assets/images/tab_archives_logo.png" alt="" style="display: block; width: 50px; margin-right: 10px;">
|
|
<div style="flex: 1;">
|
|
<div class="store-info-item store-top">
|
|
<p style="width:62%;">{{ item.warehouse.warehouseName || '仓库' }}</p>
|
|
<p style="flex:1;">面积:{{ item.warehouse.warehouseArea || '0' }} ㎡</p>
|
|
<i
|
|
style="width:6%; text-align: center; cursor: pointer;"
|
|
class="iconfont icon-wangluoshexiangtou"
|
|
@click.stop="openWarehouseCamera(item.warehouse)"
|
|
/>
|
|
</div>
|
|
<div class="store-info-item" style="font-size: 12px; color: #909399; margin-top: 10px;">
|
|
<div style="display: flex; justify-content: flex-start;">
|
|
<p v-if="item.warehouse.administrator">{{ item.warehouse.administrator }}</p>
|
|
<p v-if="item.warehouse.contactPhone"> {{ item.warehouse.contactPhone }}</p>
|
|
</div>
|
|
<p v-if="item.warehouse.lastTime">最后同步时间: {{ item.warehouse.lastTime | parseTime }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="store-monitor">
|
|
<li>
|
|
<span>当日进出</span>
|
|
<p>{{ item.todayInCount || 0 }}次</p>
|
|
</li>
|
|
<li>
|
|
<span>本月进出</span>
|
|
<p>{{ item.monthlyInCount || 0 }}次</p>
|
|
</li>
|
|
<li>
|
|
<span>本月入库</span>
|
|
<p>{{ item.monthlyInboundCount || 0 }}次</p>
|
|
</li>
|
|
<li>
|
|
<span>本月出库</span>
|
|
<p>{{ item.monthlyOutboundCount || 0 }}次</p>
|
|
</li>
|
|
<li>
|
|
<span>本月出入库异动</span>
|
|
<p>{{ item.fluctuationCount || 0 }}次</p>
|
|
</li>
|
|
<li>
|
|
<span>本月重点资产异动</span>
|
|
<p>{{ item.fluctuationImportantCount || 0 }}次</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="warning-text">
|
|
<i class="iconfont icon-baojingjilu" />
|
|
<div v-if="item.isWarningActive" :ref="el => setWarningRef(el, index)" class="warning-text-scroll">
|
|
<div class="scroll-wrap">
|
|
<div v-for="(warn, warnIdx) in item.warnList" :key="warnIdx" class="scroll-item">
|
|
{{ warn.warnValue }}
|
|
</div>
|
|
<!-- 复制一份,用于无缝循环 -->
|
|
<div v-for="(warn, warnIdx) in item.warnList" :key="'copy_'+warnIdx" class="scroll-item">
|
|
{{ warn.warnValue }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else style="font-size: 12px; color: #999; flex: 1;">暂无报警信息</div>
|
|
<i class="el-icon-more" style="font-size: 12px; cursor: pointer;" @click.stop="handleMoreClick(item)" />
|
|
</div>
|
|
</el-card>
|
|
|
|
<div v-if="warehouseCardList.length === 0 && !crud.loading" class="no-device">
|
|
<el-empty style="padding:80px 0;" :image-size="60" description="暂无仓库信息" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 监控视频弹窗 -->
|
|
<el-dialog
|
|
class="view-video"
|
|
append-to-body
|
|
:close-on-click-modal="false"
|
|
:modal-append-to-body="false"
|
|
:visible="hkVideoVisible"
|
|
title="查看监控视频"
|
|
:before-close="handleClose"
|
|
>
|
|
<span class="dialog-right-top" />
|
|
<span class="dialog-left-bottom" />
|
|
<div class="setting-dialog">
|
|
<hkVideo ref="hkVideoRef" :hk-config="hkConfig" />
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<!-- ==========新增:历史报警弹窗========== -->
|
|
<el-dialog
|
|
title="历史报警信息"
|
|
append-to-body
|
|
width="900px"
|
|
:visible.sync="alarmDialogVisible"
|
|
>
|
|
<el-table
|
|
ref="table"
|
|
class="archives-table"
|
|
stripe
|
|
style="width: 100%;"
|
|
height="calc(100vh - 530px)"
|
|
:data="alarmTableData"
|
|
>
|
|
<el-table-column prop="warehouseName" label="仓库名称" />
|
|
<el-table-column prop="warnValue" label="报警信息" />
|
|
<el-table-column prop="startTime" label="报警时间" width="160">
|
|
<template slot-scope="scope">
|
|
{{ scope.row.startTime | parseTime }}
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-pagination
|
|
style="margin-top:16px;text-align:right;"
|
|
:current-page="alarmPageNum"
|
|
:page-size="alarmPageSize"
|
|
:total="alarmTotal"
|
|
layout="total, prev, pager, next"
|
|
@current-change="alarmPageChange"
|
|
/>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import crudDevice, { FetchFondsWarehouseTree, FetchDeviceAllByWarehouseId, FetchInitAlarmsLog } from '@/api/assetDevice/index'
|
|
import CRUD, { presenter, header, crud } from '@crud/crud'
|
|
import hkVideo from '@/views/components/hkVideo.vue'
|
|
|
|
export default {
|
|
name: 'Monitor',
|
|
components: {
|
|
hkVideo
|
|
},
|
|
cruds() {
|
|
return CRUD({
|
|
title: '站点监控',
|
|
url: 'api/warehouse/warehouseMonitoring',
|
|
idField: 'warehouse.id',
|
|
sort: [],
|
|
crudMethod: { ...crudDevice },
|
|
optShow: {
|
|
add: false,
|
|
edit: false,
|
|
del: false,
|
|
reset: true,
|
|
download: false,
|
|
group: false
|
|
},
|
|
queryOnPresenterCreated: false
|
|
})
|
|
},
|
|
mixins: [presenter(), header(), crud()],
|
|
data() {
|
|
return {
|
|
permission: { add: [], edit: [], del: [] },
|
|
warehouseTreeData: null,
|
|
defaultProps: {
|
|
children: 'children',
|
|
label: 'name'
|
|
},
|
|
defaultCheckedKeys: [],
|
|
selectedTreeKey: null,
|
|
warehouseCardList: [],
|
|
warningRefs: [],
|
|
warningIntervals: [],
|
|
warningScrollTasks: [],
|
|
hkVideoVisible: false,
|
|
hkConfig: {
|
|
username: null,
|
|
password: null,
|
|
ip: null,
|
|
port: null
|
|
},
|
|
// ==========历史报警弹窗新增data==========
|
|
alarmDialogVisible: false,
|
|
alarmLoading: false,
|
|
alarmWarehouseNo: '',
|
|
alarmTableData: [],
|
|
alarmPageNum: 1,
|
|
alarmPageSize: 10,
|
|
alarmTotal: 0
|
|
}
|
|
},
|
|
created() {
|
|
FetchFondsWarehouseTree().then(res => {
|
|
const filteredData = this.filterTreeData(res)
|
|
const topParent = {
|
|
id: 0,
|
|
name: '市州',
|
|
children: filteredData
|
|
}
|
|
this.warehouseTreeData = [topParent]
|
|
this.$nextTick(() => {
|
|
this.selectFirstWarehouseNode()
|
|
})
|
|
}).catch(() => {})
|
|
},
|
|
beforeDestroy() {
|
|
this.clearAllWarningScroll()
|
|
},
|
|
methods: {
|
|
[CRUD.HOOK.beforeRefresh]() {
|
|
this.crud.params.page = null
|
|
this.crud.params.size = null
|
|
},
|
|
[CRUD.HOOK.afterRefresh]() {
|
|
const apiList = this.crud.data || []
|
|
this.buildWarehouseCardList(apiList)
|
|
},
|
|
buildWarehouseCardList(apiResult) {
|
|
const apiList = this.crud.data || []
|
|
|
|
if (!Array.isArray(apiList) || apiList.length === 0) {
|
|
this.warehouseCardList = []
|
|
return
|
|
}
|
|
let filterList = []
|
|
if (this.selectedTreeKey?.id === 0) {
|
|
filterList = apiList
|
|
} else {
|
|
const selectFondsNo = this.selectedTreeKey?.fondsNo
|
|
filterList = apiList.filter(item => item.warehouse.fondsNo === selectFondsNo)
|
|
}
|
|
|
|
const cardList = filterList.map(item => {
|
|
return {
|
|
...item,
|
|
isWarningActive: Array.isArray(item.warnList) && item.warnList.length > 0
|
|
}
|
|
})
|
|
this.warehouseCardList = cardList
|
|
console.log('cardList', cardList)
|
|
this.$nextTick(() => {
|
|
this.startAllTextScroll()
|
|
})
|
|
},
|
|
|
|
setWarningRef(el, index) {
|
|
if (el) this.warningRefs[index] = el
|
|
},
|
|
|
|
clearAllWarningScroll() {
|
|
this.warningScrollTasks.forEach(task => {
|
|
if (task && typeof task.stop === 'function') task.stop()
|
|
})
|
|
this.warningScrollTasks = []
|
|
},
|
|
|
|
startAllTextScroll() {
|
|
this.clearAllWarningScroll()
|
|
this.warningRefs.forEach(el => {
|
|
if (!el) return
|
|
const wrapDom = el.querySelector('.scroll-wrap')
|
|
if (wrapDom) {
|
|
wrapDom.style.transform = `translateY(0px)`
|
|
wrapDom.style.transition = ''
|
|
}
|
|
})
|
|
|
|
this.warehouseCardList.forEach((card, idx) => {
|
|
if (!card.isWarningActive || !card.warnList || card.warnList.length <= 0) return
|
|
const boxDom = this.warningRefs[idx]
|
|
if (!boxDom) return
|
|
const wrapDom = boxDom.querySelector('.scroll-wrap')
|
|
if (!wrapDom) return
|
|
|
|
const itemHeight = 44
|
|
const originLen = card.warnList.length
|
|
const totalOriginHeight = originLen * itemHeight
|
|
|
|
let offset = 0
|
|
let timer = null
|
|
|
|
const scrollStep = () => {
|
|
wrapDom.style.transition = 'transform 0.6s ease-in-out'
|
|
offset += itemHeight
|
|
wrapDom.style.transform = `translateY(-${offset}px)`
|
|
|
|
timer = setTimeout(() => {
|
|
if (offset >= totalOriginHeight) {
|
|
wrapDom.style.transition = 'none'
|
|
offset = 0
|
|
wrapDom.style.transform = `translateY(0px)`
|
|
wrapDom.offsetHeight
|
|
}
|
|
scrollStep()
|
|
}, 3000)
|
|
}
|
|
|
|
const stop = () => {
|
|
clearTimeout(timer)
|
|
}
|
|
this.warningScrollTasks.push({ stop })
|
|
scrollStep()
|
|
})
|
|
},
|
|
|
|
filterTreeData(tree) {
|
|
return tree.map(node => {
|
|
if (node.type === 'warehouse') return null
|
|
const newNode = { ...node }
|
|
if (newNode.children && newNode.children.length) {
|
|
newNode.children = this.filterTreeData(newNode.children).filter(item => item)
|
|
}
|
|
return newNode
|
|
}).filter(item => item)
|
|
},
|
|
|
|
selectFirstWarehouseNode() {
|
|
const root = this.warehouseTreeData[0]
|
|
if (root) {
|
|
this.defaultCheckedKeys = [root.id]
|
|
this.$refs.tree.setCurrentKey(root.id)
|
|
this.handleNodeClick(root)
|
|
}
|
|
},
|
|
|
|
handleNodeClick(data) {
|
|
this.selectedTreeKey = data
|
|
if (this.selectedTreeKey.id === 0) {
|
|
this.crud.query.warehouseId = null
|
|
this.crud.query.fondsId = null
|
|
} else {
|
|
this.crud.query.fondsId = data.id
|
|
}
|
|
this.search = ''
|
|
this.crud.toQuery()
|
|
},
|
|
|
|
handleClose() {
|
|
this.hkVideoVisible = false
|
|
},
|
|
async openWarehouseCamera(cardItem) {
|
|
const warehouseId = cardItem.id
|
|
try {
|
|
const res = await FetchDeviceAllByWarehouseId({ warehouseId })
|
|
const allDevices = res || []
|
|
|
|
const ballDevices = allDevices.filter(d => d.deviceTypeName === '网络高清球机')
|
|
|
|
if (ballDevices.length === 0) {
|
|
this.$message.info('该仓库暂无网络高清球机设备')
|
|
return
|
|
}
|
|
console.log('当前仓库匹配的球机', ballDevices)
|
|
this.handleViewVideo(ballDevices[0].id)
|
|
} catch (err) {
|
|
console.error('获取仓库设备异常:', err)
|
|
this.$message.error('获取仓库设备失败')
|
|
}
|
|
},
|
|
|
|
async handleViewVideo(deviceId) {
|
|
try {
|
|
const [detailRes, pwdRes] = await Promise.all([
|
|
crudDevice.FetchDeviceDetailsById({ deviceId }),
|
|
crudDevice.FetchDevicePasswordById({ deviceId })
|
|
])
|
|
if (!detailRes) return
|
|
const finalPassword = pwdRes || detailRes.devicePassword
|
|
this.hkConfig = {
|
|
username: detailRes.deviceAccount,
|
|
password: finalPassword,
|
|
ip: detailRes.deviceIp,
|
|
port: detailRes.devicePort
|
|
}
|
|
this.hkVideoVisible = true
|
|
this.$nextTick(() => {
|
|
this.$refs.hkVideoRef.initVideo()
|
|
})
|
|
} catch (err) {
|
|
console.error('打开视频失败:', err)
|
|
this.$message.error('获取监控设备信息失败')
|
|
}
|
|
},
|
|
|
|
// 修改后 handleMoreClick
|
|
handleMoreClick(item) {
|
|
this.alarmWarehouseNo = item.warehouse.warehouseNo
|
|
this.alarmPageNum = 1
|
|
this.alarmDialogVisible = true
|
|
this.getAlarmList()
|
|
},
|
|
// 获取报警列表
|
|
async getAlarmList() {
|
|
this.alarmLoading = true
|
|
try {
|
|
const param = {
|
|
warehouseNo: this.alarmWarehouseNo,
|
|
pageNum: this.alarmPageNum,
|
|
pageSize: this.alarmPageSize
|
|
}
|
|
const res = await FetchInitAlarmsLog(param)
|
|
this.alarmTableData = res.records || []
|
|
this.alarmTotal = res.total || 0
|
|
} catch (e) {
|
|
this.$message.error('查询历史报警失败')
|
|
} finally {
|
|
this.alarmLoading = false
|
|
}
|
|
},
|
|
// 分页切换
|
|
alarmPageChange(page) {
|
|
this.alarmPageNum = page
|
|
this.getAlarmList()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
::v-deep .el-tree {
|
|
.el-tree-node__content {
|
|
font-size: 14px;
|
|
color: #545B65;
|
|
}
|
|
.el-tree-node__children {
|
|
.el-tree-node__content {
|
|
font-size: 14px;
|
|
color: #545B65;
|
|
}
|
|
}
|
|
}
|
|
|
|
.monitor-list {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
padding-top: 15px;
|
|
.list-card {
|
|
width: calc(100% / 3 - 20px);
|
|
margin: 0 10px 20px 10px;
|
|
}
|
|
}
|
|
|
|
::v-deep .el-card {
|
|
border-radius: 10px;
|
|
.el-card__header {
|
|
height: 66px;
|
|
padding: 12px 10px !important;
|
|
border-radius: 10px 10px 0 0;
|
|
color: #0c0e1e;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
background-color: #fff !important;
|
|
border-bottom: 1px solid #ebeef5 !important;
|
|
.store-info {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
.store-info-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
p {
|
|
margin-right: 6px;
|
|
}
|
|
&.store-top {
|
|
justify-content: space-between;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.el-card__body {
|
|
padding: 5px 10px;
|
|
border-radius: 0 0 10px 10px;
|
|
.store-monitor {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-start;
|
|
align-items: flex-start;
|
|
padding: 5px 5px 10px 5px;
|
|
line-height: 35px;
|
|
font-size: 14px;
|
|
li {
|
|
width: calc(50% - 10px);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
&:nth-child(even) {
|
|
margin-left: 15px;
|
|
}
|
|
span {
|
|
display: block;
|
|
width: 120px;
|
|
}
|
|
p {
|
|
flex: 1;
|
|
text-align: right;
|
|
font-weight: bold;
|
|
color: #0C0E1E;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.warning-text {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
border-top: 1px solid #ebeef5;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
height: 44px;
|
|
overflow: hidden;
|
|
padding: 0 10px;
|
|
|
|
.iconfont {
|
|
flex-shrink: 0;
|
|
color: #F65163;
|
|
}
|
|
.warning-text-scroll {
|
|
flex: 1;
|
|
height: 44px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
.scroll-wrap {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
}
|
|
.scroll-item {
|
|
height: 44px;
|
|
line-height: 44px;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
&.warning-active {
|
|
.el-card__header {
|
|
background-color: rgba(85, 85, 85, 0.2) !important;
|
|
border-bottom: 1px solid #fff !important;
|
|
}
|
|
.el-card__body {
|
|
background-color: rgba(85, 85, 85, 0.2) !important;
|
|
}
|
|
.warning-text {
|
|
border-top: 1px solid #fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
.view-video {
|
|
::v-deep .el-dialog {
|
|
width: 1000px !important;
|
|
}
|
|
}
|
|
</style>
|