主页
< MapZrender ref = " imgZrender" class = " map-zrender" :map-bg = " mapBg" :track-list = " trackList"
:configed-cameras = " routePoints" @play-video = " handlePlayVideo" />
配置页面
< MapZrender ref = " imgZrender" class = " map-zrender" :map-bg = " mapBg" :is-active = " isActive"
:related-cameras = " relatedCameras" :configed-cameras = " configedCameras" :point-type = " pointType"
:is-config = " true" @change-active = " changeActive" @add-point = " addPoint" @remove-point = " removePoint"
@play-video = " handlePlayVideo" />
MapZrender 组件
< template>
< div class = " map-container" >
< div id = " map" @contextmenu.prevent > </ div>
< div v-show = " detailList?.length" id = " detailList" class = " detail-list" >
< el-button type = " ghost" icon = " h-icon-close" class = " btn-close" @click = " detailList = []" > </ el-button>
< div v-for = " (item, index) in detailList" :key = " `${item.certificateNumber}_${index}`" class = " card" >
< img :src = " imgToNgix(item.facePicUrl)" width = " 92" height = " 92" @click = " handlePreview(item.bkgUrl)"
@error = " handleError" />
< div class = " card__right" >
< div class = " field" >
< div class = " field-label" > 时间:</ div>
< div class = " field-value" > {{ item.faceTime }}</ div>
</ div>
< div class = " field" >
< div class = " field-label" > 地点:</ div>
< div class = " field-value" :title = " item.cameraName" > {{ item.cameraName }}</ div>
</ div>
< div class = " field" >
< div class = " field-label" style = " width : 56px; " > 相似度:</ div>
< div class = " field-value" style = " width : calc ( 100% - 56px) ; " > {{ item.similarity || '--' }}%</ div>
</ div>
< div class = " field" >
< el-button size = " small" @click = " handlePlay(item)" > 预览</ el-button>
< el-button size = " small" @click = " handlePlayback(item)" > 回放</ el-button>
</ div>
</ div>
</ div>
</ div>
</ div>
</ template>
< script>
import * as zrender from 'zrender/dist/zrender'
let zr
export default {
props: {
mapBg: {
type: String,
default : ''
} ,
isActive: {
type: Boolean,
default : false
} ,
relatedCameras: {
type: Array,
default : ( ) => [ ]
} ,
configedCameras: {
type: Array,
default : ( ) => [ ]
} ,
pointType: {
type: String,
default : 'common'
} ,
trackList: {
type: Array,
default : ( ) => [ ]
} ,
isConfig: {
type: Boolean,
default : false
}
} ,
data ( ) {
return {
container: null ,
containerWidth: 1 ,
containerHeight: 1 ,
group: null ,
elements: [ ] ,
pointGroup: null ,
lineGroup: null ,
zrBgImg: null ,
titleTip: '' ,
popupElement: null ,
positionImg: require ( '@/assets/images/position.png' ) ,
cameraIcon: require ( '@/assets/images/camera.png' ) ,
posZrImage: null ,
index: 1 ,
time: 1000 ,
posX: 200 ,
posY: 200 ,
direction: '' ,
requestID: null ,
detailList: [ ] ,
defaultImg: require ( '@/assets/images/default-person.png' ) ,
}
} ,
watch: {
mapBg: {
handler : function ( val ) {
val && this . addMapBg ( )
}
} ,
isActive ( val ) {
this . zrBgImg?. attr ( {
cursor: val ? 'pointer' : 'unset'
} )
} ,
configedCameras: {
handler : function ( val ) {
console. log ( val)
this . batchAddPoints ( val)
}
} ,
trackList: {
handler : function ( val ) {
this . drawLines ( )
}
}
} ,
mounted ( ) {
this . init ( )
this . popupElement = document. getElementById ( 'detailList' )
} ,
methods: {
init ( ) {
this . container = document. getElementById ( 'map' )
this . containerWidth = this . container. clientWidth
this . containerHeight = this . container. clientHeight
zr = zrender. init ( this . container)
this . group = new zrender. Group ( )
zr. add ( this . group)
this . pointGroup = new zrender. Group ( )
this . group. add ( this . pointGroup)
this . lineGroup = new zrender. Group ( )
this . group. add ( this . lineGroup)
if ( this . mapBg) {
this . addMapBg ( )
}
if ( this . configedCameras?. length) {
this . batchAddPoints ( this . configedCameras)
}
if ( this . trackList?. length) {
this . drawLines ( )
}
} ,
addMapBg ( ) {
const img = new Image ( )
img. crossOrigin = 'Anonymous'
img. setAttribute ( 'crossOrigin' , 'Anonymous' )
img. src = this . mapBg
img. onload = ( ) => {
this . zrBgImg = new zrender. Image ( {
style: {
image: this . mapBg,
x: 0 ,
y: 0 ,
width: this . containerWidth,
height: this . containerHeight,
} ,
z: 1 ,
cursor: 'unset' ,
} )
this . group. add ( this . zrBgImg)
this . elements. push ( this . zrBgImg)
this . zrBgImg. on ( 'click' , data => {
if ( this . isActive) {
console. log ( data, 'zrBgImg clicked' )
const { offsetX, offsetY } = data
const x = offsetX / this . containerWidth
const y = offsetY / this . containerHeight
if ( this . pointType === 'marker' ) {
const timeStamp = new Date ( ) . getTime ( )
this . $emit ( 'add-point' , {
xPoint: x,
yPoint: y,
cameraIndexcode: ` p ${ String ( timeStamp) . substring ( 9 , 13 ) } ` ,
cameraName: ` p ${ String ( timeStamp) . substring ( 9 , 13 ) } ` ,
color: '#ff0000' ,
type: 'marker'
} )
} else {
this . $emit ( 'add-point' , {
xPoint: x,
yPoint: y,
cameraIndexcode: this . relatedCameras. map ( item => item. value) . join ( ',' ) ,
cameraName: this . relatedCameras. map ( item => item. label) . join ( ',' ) ,
color: '#00ff00' ,
type: 'common'
} )
}
}
} )
}
} ,
addPoint ( { x, y, id, cameraName, type, detailList, cameraIndexcode } ) {
let point
if ( type === 'marker' ) {
point = new zrender. Circle ( {
shape: {
cx: x * this . containerWidth,
cy: y * this . containerHeight,
r: 5
} ,
style: {
fill: '#ff0000' ,
stroke: '#ff0000' ,
} ,
z: 3 ,
id,
name: cameraName,
cursor: 'pointer' ,
} )
} else {
point = new zrender. Image ( {
style: {
image: this . cameraIcon,
x: x * this . containerWidth - 20 ,
y: y * this . containerHeight - 46 ,
width: 41 ,
height: 46 ,
} ,
z: 3 ,
id,
name: cameraName,
} )
if ( ! this . isConfig) {
point. on ( 'mouseover' , data => {
this . detailList = detailList
const {
target: {
style: { x, y } ,
} ,
} = data
if ( this . containerWidth - data. offsetX < 300 ) {
this . popupElement. style. left = ` ${ x - 290 } px `
} else {
this . popupElement. style. left = ` ${ x + 40 } px `
}
if ( this . detailList. length > 1 ) {
if ( this . containerHeight - data. offsetY < 226 ) {
this . popupElement. style. top = ` ${ y - 216 } px `
} else {
this . popupElement. style. top = ` ${ y + 23 } px `
}
} else {
if ( this . containerHeight - data. offsetY < 118 ) {
this . popupElement. style. top = ` ${ y - 118 } px `
} else {
this . popupElement. style. top = ` ${ y + 23 } px `
}
}
} )
point. on ( 'mouseout' , ( ) => {
} )
} else {
point. on ( 'click' , data => {
console. log ( data, cameraIndexcode)
this . $emit ( 'play-video' , {
playType: 'real' ,
cameraIndexCode: cameraIndexcode,
faceTime: '' ,
cameraName: cameraName,
timeStamp: new Date ( ) . getTime ( )
} )
} )
}
}
this . pointGroup. add ( point)
if ( this . isConfig) {
point. on ( 'contextmenu' , data => {
this . $confirm ( '是否确定删除?' , {
confirmButtonText: '确定' ,
cancelButtonText: '取消' ,
} )
. then ( ( ) => {
const { target: { id } } = data
this . $emit ( 'remove-point' , id)
} )
. catch ( ( ) => {
this . $message ( {
type: 'success' ,
message: '已取消删除!' ,
} )
} )
} )
}
let textOffsetY = type === 'marker' ? 30 : 56
const zrText = new zrender. Text ( {
culling: true ,
style: {
x: x * this . containerWidth,
y: y * this . containerHeight - textOffsetY,
text: cameraName,
fill: '#ffffff' ,
width: 22 ,
height: 22 ,
align: 'center' ,
verticalAlign: 'middle' ,
fontFamily: 'HTYPEtest01-Medium' ,
fontSize: 16 ,
fontWeight: 400 ,
} ,
z: 4 ,
} )
this . pointGroup. add ( zrText)
} ,
batchAddPoints ( data ) {
this . pointGroup. removeAll ( )
zr. refresh ( )
this . $emit ( 'change-active' , false )
this . $nextTick ( ( ) => {
data. forEach ( item => {
this . addPoint ( item)
} )
} )
} ,
drawLines ( ) {
this . lineGroup. removeAll ( )
zr. refresh ( )
if ( this . trackList?. length) {
this . addPositionIcon ( this . trackList[ 0 ] [ 0 ] * this . containerWidth - 16 , this . trackList[ 0 ] [ 1 ] * this . containerHeight - 32 )
const points = this . trackList. map ( item => {
return [ item[ 0 ] * this . containerWidth, item[ 1 ] * this . containerHeight]
} )
const polyline1 = new zrender. Polyline ( {
shape: {
points,
smooth: 0 ,
} ,
style: {
lineWidth: 5 ,
stroke: '#F7B71F' ,
} ,
z: 2 ,
} )
this . lineGroup. add ( polyline1)
this . time = 10000 / ( this . trackList. length - 1 )
this . index = 1
this . animate ( )
}
} ,
addPositionIcon ( x, y ) {
this . posZrImage = new zrender. Image ( {
style: {
image: this . positionImg,
x,
y,
width: 32 ,
height: 32 ,
} ,
z: 5 ,
} )
this . lineGroup. add ( this . posZrImage)
} ,
animate ( ) {
this . posZrImage
. animate ( 'style' , false )
. when ( this . time, {
x: this . trackList[ this . index] [ 0 ] * this . containerWidth - 16 ,
y: this . trackList[ this . index] [ 1 ] * this . containerHeight - 32 ,
} )
. done ( ( ) => {
this . index++
if ( this . index < this . trackList. length) {
this . animate ( )
}
} ) . start ( )
} ,
handlePreview ( url ) {
this . $parent. $parent. $parent. handlePreview ( url)
} ,
handleError ( e ) {
e. target. src = this . defaultImg;
} ,
handlePlay ( { cameraIndexCode, faceTime, cameraName } ) {
this . $emit ( 'play-video' , {
playType: 'real' ,
cameraIndexCode: cameraIndexCode,
faceTime: faceTime,
cameraName: cameraName,
timeStamp: new Date ( ) . getTime ( )
} )
} ,
handlePlayback ( { cameraIndexCode, faceTime, cameraName } ) {
this . $emit ( 'play-video' , {
playType: 'playback' ,
cameraIndexCode: cameraIndexCode,
faceTime: faceTime,
cameraName: cameraName,
timeStamp: new Date ( ) . getTime ( )
} )
} ,
}
}
</ script>
< style lang = " scss" scoped >
.map-container {
width : 100%;
height : 100%;
position : relative;
}
#map {
width : 100%;
height : 100%;
position : relative;
}
#tip {
position : absolute;
border : 1px solid #4d4d4d;
background-color : #fff;
padding : 4px;
z-index : 1;
}
.detail-list {
width : 322px;
max-height : 280px;
padding : 15px;
overflow : auto;
background-color : #f0f0f0;
position : absolute;
z-index : 1;
.btn-close {
position : absolute;
top : 0;
right : 0;
width : 24px;
height : 24px;
min-width : 24px !important ;
z-index : 2;
}
.card {
width : 100%;
height : 120px;
background-color : #ffffff;
display : flex;
align-items : center;
border-radius : 8px;
padding : 6px;
// cursor : pointer;
&:not(:last-child) {
margin-bottom : 10px;
}
// &:hover {
// background-color : rgba ( 255, 255, 255, 0.5) ;
// }
img {
float : left;
cursor : pointer;
}
&__right {
width : calc ( 100% - 98px) ;
height : 100px;
float : left;
margin-left : 6px;
display : flex;
flex-direction : column;
justify-content : space-evenly;
}
.field {
width : 100%;
display : flex;
align-items : center;
&-label {
width : 42px;
font-family : PingFangSC-Regular;
color : rgba ( 0, 0, 0, 0.40) ;
}
&-value {
width : calc ( 100% - 42px) ;
font-family : PingFangSC-Semibold;
color : #000000;
// font-weight : 600;
overflow : hidden;
text-overflow : ellipsis;
white-space : nowrap;
}
}
}
.active-card {
background-color : rgb ( 48, 140, 247) ;
color : #ffffff;
&:hover {
background-color : rgb ( 48, 140, 247) ;
}
.row-right {
background : linear-gradient ( 270deg, rgba ( 225, 225, 225, 0.2) 18%, rgba ( 225, 225, 225, 0.2) 99%) ;
}
}
}
</ style>