<template>
|
<div class="label-mark">
|
<el-tabs type="border-card" v-model="actPage">
|
<el-tab-pane label="摄像机标注" name="1">
|
<div class="mark-interface">
|
<div class="left-tree">
|
<div class="resize-bar"></div>
|
<div class="resize-line"></div>
|
<div class="resize-save">
|
<left-nav :appName="'Camera'" :height="screenHeight - 40"></left-nav>
|
</div>
|
</div>
|
<div class="tree-right">
|
<div class="action-bar">
|
<div class="tool-bar">
|
<div>
|
<!-- <input type="color" ref="colorPicker" v-model="color"> -->
|
<label for>拾色器:</label>
|
<el-color-picker v-model="colorPick" show-alpha size="mini"></el-color-picker>
|
</div>
|
<div style="width:250px;">
|
<label for>笔触:</label>
|
<el-slider v-model="dotSize" :min="1" :max="15"></el-slider>
|
</div>
|
<div>
|
<el-button
|
v-if="!isEdit"
|
class="drawboard-trigger"
|
size="small"
|
@click="editCameraData"
|
icon="el-icon-edit"
|
>编辑</el-button>
|
|
<el-button
|
v-if="isEdit"
|
class="drawboard-trigger save"
|
size="small"
|
@click="submitInfo"
|
icon="el-icon-lock"
|
>保存</el-button>
|
</div>
|
</div>
|
</div>
|
<div class="drawboard shadow-box">
|
<div class="mask" :class="{'edit-status-mask':isEdit}" ref="editBoard">
|
<div
|
class="label"
|
@click="editLabel(item)"
|
v-for="(item,index) in curCameraData.coords"
|
:key="index"
|
:style="{left:`${item.x0}px`, top:`${item.y0}px`, backgroundColor: colorPick, width: `${dotSize}px`, height: `${dotSize}px` }"
|
></div>
|
</div>
|
<img v-show="snapshot_url" :src="`/httpImage/${snapshot_url}`" alt />
|
<div
|
class="popBox"
|
v-show="isShowPop"
|
:style="`top:${curLabel.y0 + 22}px;left:${curLabel.x0}px`"
|
>
|
<div class="title">标注信息</div>
|
<div class="details">
|
<el-form :model="curLabel" :rules="rules" ref="labelForm">
|
<div class="detail-item">
|
<div class="left">
|
<el-form-item prop="x0">
|
<label for>平面坐标X:</label>
|
<span class="fix-width">{{curLabel.x0}}</span>
|
<i>px</i>
|
</el-form-item>
|
</div>
|
<span class="devide"></span>
|
<div class="right">
|
<el-form-item prop="x1">
|
<label for>实际坐标X:</label>
|
<el-input
|
type="text"
|
size="mini"
|
style="width:90px"
|
v-model.number="curLabel.x1"
|
></el-input>
|
</el-form-item>
|
</div>
|
</div>
|
<div class="detail-item">
|
<div class="left">
|
<el-form-item prop="y0">
|
<label for>平面坐标Y:</label>
|
<span class="fix-width">{{curLabel.y0}}</span>
|
<i>px</i>
|
</el-form-item>
|
</div>
|
<span class="devide"></span>
|
<div class="right">
|
<el-form-item prop="y1">
|
<label for>实际坐标Y:</label>
|
<el-input
|
type="text"
|
size="mini"
|
style="width:90px"
|
v-model.number="curLabel.y1"
|
></el-input>
|
</el-form-item>
|
</div>
|
</div>
|
<div class="btns">
|
<el-button size="mini" type="danger" @click="deleteLabel">删除</el-button>
|
<el-button size="mini" type="primary" @click="cancle">取消</el-button>
|
<el-button size="mini" type="success" @click="sure">确定</el-button>
|
</div>
|
</el-form>
|
</div>
|
</div>
|
</div>
|
<div class="panorama-info">
|
<div
|
class="img-wrap"
|
v-if="panoramaPath"
|
:style="{width:fixedW+'px',height:fixedH+'px'}"
|
>
|
<div class="img-area" :style="{width:imgW+'px',height:imgH+'px'}">
|
<img
|
v-if="panoramaPath"
|
:style="{width:imgW+'px',height:imgH+'px'}"
|
:src="panoramaPath"
|
@mousemove="showCurPos"
|
@mouseout="isShowCurPos=false"
|
/>
|
<div
|
class="label"
|
@mouseover="labelOver(item)"
|
@mouseout="item.isShow=false"
|
v-for="item in curCameraData.panoramaCoords"
|
:key="item.id"
|
:style="{left:`${item.x}px`, top:`${item.y}px`, backgroundColor: colorPick, width: `${dotSize}px`, height: `${dotSize}px` }"
|
>
|
<span class="text" v-show="item.isShow">
|
<b>{{item.x}},</b>
|
<b>{{item.y}}</b>
|
</span>
|
</div>
|
</div>
|
</div>
|
<div class="pos" v-show="isShowCurPos">
|
当前位置:
|
<b>{{traceX}}</b>,
|
<b>{{traceY}}</b>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-tab-pane>
|
<el-tab-pane label="实景坐标" name="2">
|
<div class="user-upload">
|
<div class="img-card">
|
<el-upload
|
class="upload-demo"
|
drag
|
action="https://jsonplaceholder.typicode.com/posts/"
|
:http-request="definedUpload"
|
accept=".jpg"
|
:on-change="onChange"
|
:show-file-list="false"
|
>
|
<!-- <el-image
|
class="preview"
|
v-if="panoramaPath"
|
:src="panoramaPath"
|
fit="contain"
|
@mousemove="showCurPos"
|
@mouseout="isShowCurPos=false"
|
></el-image>-->
|
<div
|
class="img-wrap"
|
v-if="panoramaPath"
|
:style="{width:fixedW+'px',height:fixedH+'px'}"
|
>
|
<img
|
v-if="panoramaPath"
|
:style="{width:imgW+'px',height:imgH+'px'}"
|
:src="panoramaPath"
|
@mousemove="showCurPos"
|
@mouseout="isShowCurPos=false"
|
/>
|
</div>
|
|
<div class="el-upload__text">
|
将文件拖到此处,或
|
<em>点击上传</em>
|
</div>
|
</el-upload>
|
</div>
|
<div class="info">
|
<div class="input-area">
|
<!-- <div>
|
<label for>空间宽:</label>
|
<el-input v-model="spaceWidth" placeholder="请输入实际空间宽" size="small"></el-input>
|
</div>
|
<div>
|
<label for>空间高:</label>
|
<el-input v-model="spaceHeight" placeholder="请输入实际空间高" size="small"></el-input>
|
</div>-->
|
</div>
|
<div class="pos" v-show="isShowCurPos">
|
当前位置:
|
<b>{{traceX}}</b>,
|
<b>{{traceY}}</b>
|
</div>
|
</div>
|
</div>
|
</el-tab-pane>
|
</el-tabs>
|
</div>
|
</template>
|
|
<script>
|
import { getCamerasByServer } from '@/api/pollConfig';
|
import { getCameraMarks, updateCameraMarks } from '@/api/camera';
|
import { getPanoramaPic, putPanoramaPic } from '@/api/panorama';
|
import { isNonnegativeInteger } from '@/scripts/validate';
|
import LeftNav from "@/components/LeftNav";
|
export default {
|
components: { LeftNav },
|
data() {
|
return {
|
screenHeight: 0,
|
labels: [],
|
colorPick: '#79f2fb',
|
dotSize: 3,
|
isEdit: false,
|
isShowPop: false,
|
isNewLabel: false,
|
curLabel: {
|
id: '',
|
x1: '',
|
y1: '',
|
x0: '',
|
y0: ''
|
},
|
rules: {
|
x1: [
|
{ validator: isNonnegativeInteger, trigger: 'change' }
|
],
|
y1: [
|
{ validator: isNonnegativeInteger, trigger: 'change' }
|
]
|
},
|
baseUrl: '',
|
snapshot_url: '',
|
panoramaPath: '',
|
cameraData: [],
|
fixedW: 960,
|
fixedH: 700,
|
imgW: 0,
|
imgH: 0,
|
traceX: 0,
|
traceY: 0,
|
isShowCurPos: false,
|
actPage: '1',
|
loading: false,
|
spaceWidth: '',
|
spaceHeight: '',
|
curCameraData: {
|
cameraId: '',
|
coords: [],
|
panoramaCoords: []
|
},
|
}
|
},
|
mounted() {
|
this.getAllCameraData();
|
this.getPanorama();
|
this.screenHeight = document.documentElement.clientHeight - 20;
|
window.onresize = () => {
|
return (() => {
|
this.screenHeight = document.documentElement.clientHeight - 20;
|
})();
|
};
|
},
|
|
watch: {
|
'TreeDataPool.selectedNode': {
|
handler(n, o) {
|
let curCamera = this.cameraData.find(item => item.id == n.id);
|
//设置摄像机底图
|
this.snapshot_url = curCamera.snapshot_url;
|
this.findCameraMarks(n.id);
|
},
|
deep: true
|
},
|
isEdit(n, o) {
|
if (n) {
|
this.$refs['editBoard'].addEventListener('click', this.bindListen);
|
} else {
|
this.$refs['editBoard'].removeEventListener('click', this.bindListen);
|
}
|
}
|
},
|
methods: {
|
labelOver(item) {
|
// debugger
|
this.$nextTick(() => {
|
item.isShow = true;
|
})
|
},
|
labelout(item) {
|
item.isShow = false;
|
},
|
getAllCameraData() {
|
let _this = this;
|
getCamerasByServer().then(res => {
|
if (res.success) {
|
_this.cameraData = res.data;
|
//_this.getAllGroups();
|
}
|
}).catch(e => {
|
console.log(e)
|
})
|
},
|
sure() {
|
let _this = this;
|
this.$refs['labelForm'].validate(valid => {
|
if (valid) {
|
_this.isShowPop = false;
|
//编辑确定
|
if (_this.curLabel.id) {
|
let editedIndex = _this.curCameraData.coords.findIndex(one => one.id == _this.curLabel.id);
|
_this.curCameraData.coords[editedIndex] = JSON.parse(JSON.stringify(_this.curLabel));
|
|
}
|
this.$refs['labelForm'].clearValidate();
|
}
|
});
|
},
|
//获取摄像机标注
|
findCameraMarks(id) {
|
getCameraMarks({ cameraId: id }).then(res => {
|
if (res.success) {
|
this.curCameraData.cameraId = id;
|
this.curCameraData.coords = res.data.coords.map((item, index) => ({ id: 'm' + index, x0: item.x0, y0: item.y0, x1: item.x1, y1: item.y1 }));
|
this.curCameraData.panoramaCoords = res.data.panoramaCoords.map((item, index) => ({ id: 'p' + index, x: item.x, y: item.y }));
|
this.curCameraData.panoramaCoords.forEach(coord => {
|
this.$set(coord, 'isShow', false)
|
})
|
}
|
}).catch(e => {
|
console.log(e)
|
});
|
},
|
editCameraData() {
|
if (!this.TreeDataPool.selectedNode.id) {
|
this.$notify({
|
message: '请先选择摄像机',
|
type: 'warning'
|
});
|
return;
|
}
|
this.isEdit = !this.isEdit;
|
},
|
async submitInfo() {
|
this.isEdit = false;
|
|
if (this.curCameraData.coords.length > 0 && this.curCameraData.coords.length < 4) {
|
this.$message({
|
type: "warning",
|
message: "保存失败! 至少需要标记4处!"
|
})
|
return
|
}
|
|
let res = await updateCameraMarks(this.curCameraData);
|
if (res.success) {
|
this.$message({
|
type: "success",
|
message: "保存成功"
|
})
|
this.findCameraMarks(this.curCameraData.cameraId);
|
}
|
},
|
getPanorama() {
|
let _this = this;
|
getPanoramaPic().then(res => {
|
//判断长宽比
|
let ratio = res.data.width / res.data.height;
|
if (ratio > (_this.fixedW / _this.fixedH)) {
|
_this.imgW = _this.fixedW;
|
_this.imgH = _this.imgW * res.data.height / res.data.width;
|
} else {
|
_this.imgH = _this.fixedH;
|
_this.imgW = res.data.width * _this.imgH / res.data.height;
|
}
|
_this.panoramaPath = res.data.panoramaPath + '?' + Math.random();
|
})
|
},
|
showCurPos(e) {
|
this.isShowCurPos = true;
|
this.traceX = e.offsetX;
|
this.traceY = e.offsetY;
|
},
|
onChange(file, fileList) {
|
if (file.raw.type == "image/jpeg") {
|
fileList = [file]
|
this.isShowCurPos = false;
|
}
|
|
},
|
definedUpload(params) {
|
let _this = this;
|
let _file = params.file;
|
let param = new FormData();
|
param.append('file', params.file)
|
putPanoramaPic(param).then(res => {
|
// debugger
|
//_this.panoramaPath = res.data.panoramaPath + '?' + Math.random();
|
_this.getPanorama()
|
_this.$parent.$refs['tracePlot'] && _this.$parent.$refs['tracePlot'].getPanorama();
|
})
|
|
},
|
|
bindListen(e) {
|
this.newLabel(e);
|
},
|
newLabel(e) {
|
if (this.isShowPop) return;
|
//获取鼠标相对于画板的定位
|
this.$refs['labelForm'].resetFields();
|
let target = {
|
id: '',
|
x0: e.offsetX,
|
y0: e.offsetY,
|
x1: '',
|
y1: ''
|
};
|
target.id = 'n' + (this.curCameraData.coords.length - 1);
|
//this.labels.push(target);
|
this.curCameraData.coords.push(target);
|
this.curLabel = target;
|
this.isShowPop = true;
|
this.isNewLabel = true;
|
},
|
editLabel(label) {
|
if (!this.isEdit) return;
|
this.isShowPop = true;
|
this.$refs['labelForm'].clearValidate();
|
this.curLabel = JSON.parse(JSON.stringify(label));
|
|
},
|
cancle() {
|
this.isShowPop = false;
|
//如果是未保存过的label直接删除(未保存的就是labels数组中最后一个)
|
if (this.curLabel.id.startsWith('n')) {
|
//this.labels.pop();
|
this.curCameraData.coords.pop();
|
}
|
},
|
deleteLabel() {
|
if (this.curLabel.id) {
|
let index = this.curCameraData.coords.findIndex(item => item.id == this.curLabel.id);
|
this.curCameraData.coords.splice(index, 1);
|
|
} else {
|
//this.labels.pop();
|
this.curCameraData.coords.pop();
|
}
|
this.isShowPop = false;
|
},
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
.resize-save {
|
position: absolute;
|
top: 0;
|
right: 5px;
|
bottom: 0;
|
left: 0;
|
padding: 14px;
|
overflow-x: hidden;
|
}
|
.resize-bar {
|
width: 310px;
|
//height: inherit;
|
resize: horizontal;
|
cursor: ew-resize;
|
opacity: 0;
|
overflow: scroll;
|
max-width: 500px; //设定最大拉伸长度
|
min-width: 33px; //设定最小宽度
|
}
|
/* 拖拽线 */
|
.resize-line {
|
position: absolute;
|
right: 0;
|
top: 0;
|
bottom: 0;
|
border-right: 2px solid #efefef;
|
border-left: 1px solid #e0e0e0;
|
pointer-events: none;
|
}
|
.resize-bar:hover ~ .resize-line,
|
.resize-bar:active ~ .resize-line {
|
border-left: 1px dashed skyblue;
|
}
|
.resize-bar::-webkit-scrollbar {
|
width: 200px;
|
height: inherit;
|
}
|
|
/* Firefox只有下面一小块区域可以拉伸 */
|
@supports (-moz-user-select: none) {
|
.resize-bar:hover ~ .resize-line,
|
.resize-bar:active ~ .resize-line {
|
border-left: 1px solid #bbb;
|
}
|
.resize-bar:hover ~ .resize-line::after,
|
.resize-bar:active ~ .resize-line::after {
|
content: "";
|
position: absolute;
|
width: 16px;
|
height: 16px;
|
bottom: 0;
|
right: -8px;
|
// background: url(./resize.svg);
|
background-size: 100% 100%;
|
}
|
}
|
.label-mark {
|
background: #d2dcea;
|
height: calc(100vh - 61px);
|
& > .el-tabs--border-card > .el-tabs__content {
|
background: #d2dcea;
|
padding: 0;
|
height: calc(100vh - 100px);
|
//overflow: auto;
|
.mark-interface {
|
display: flex;
|
.left-tree {
|
position: relative;
|
//float: left;
|
height: calc(100vh - 20px);
|
overflow: auto;
|
background: #fff;
|
}
|
.tree-right {
|
flex: 1;
|
height: calc(100vh - 90px);
|
overflow: auto;
|
}
|
}
|
}
|
.tool-bar {
|
//width: 40px;
|
height: 100%;
|
padding: 10px 0 10px 20px;
|
box-sizing: border-box;
|
//background: rgb(250, 250, 250);
|
background: rgba(26, 45, 74, 0.6);
|
//margin-bottom: 40px;
|
display: flex;
|
align-items: center;
|
justify-content: flex-end;
|
> div {
|
cursor: pointer;
|
//background: rgba(245, 245, 245, 0.3);
|
padding: 0 5px;
|
height: 40px;
|
margin: 7px;
|
display: flex;
|
align-items: center;
|
label {
|
margin-right: 10px;
|
color: rgb(161, 161, 161);
|
color: #fff;
|
}
|
.el-slider {
|
width: 110px;
|
}
|
}
|
}
|
.shadow-box {
|
box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.1);
|
}
|
.action-bar {
|
width: 960px;
|
// margin: auto;
|
margin-left: 100px;
|
margin-top: 30px;
|
//margin-bottom: 20px;
|
text-align: right;
|
.drawboard-trigger {
|
background: transparent;
|
color: #fff;
|
border-color: rgba(255, 255, 255, 0.3);
|
}
|
}
|
.drawboard {
|
// margin: auto;
|
margin-left: 100px;
|
width: 960px;
|
height: 540px;
|
margin-bottom: 130px;
|
position: relative;
|
//background: #fff;
|
background: #f0ffca;
|
//box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.1);
|
.mask {
|
position: absolute;
|
background: transparent;
|
width: 100%;
|
height: 100%;
|
overflow: hidden;
|
&.edit-status-mask {
|
background: rgba(20, 181, 255, 0.1);
|
}
|
.label {
|
position: absolute;
|
z-index: 2;
|
border-radius: 50%;
|
}
|
}
|
img {
|
width: 960px;
|
height: 540px;
|
background: #f0ffca;
|
}
|
.right-panel {
|
width: 150px;
|
height: 100%;
|
background: rgba(26, 45, 74, 0.7);
|
}
|
.popBox {
|
position: absolute;
|
z-index: 99;
|
padding: 14px;
|
border-radius: 3px;
|
color: #fff;
|
background: rgba(26, 45, 74, 0.7);
|
.title {
|
font-weight: bold;
|
text-align: left;
|
font-size: 15px;
|
margin-bottom: 14px;
|
letter-spacing: 2px;
|
}
|
.details {
|
.detail-item {
|
display: flex;
|
margin: 5px 0;
|
label {
|
color: #a9a9a9;
|
width: 65px;
|
display: inline-block;
|
}
|
.left {
|
width: 110px;
|
text-align: left;
|
line-height: 28px;
|
.fix-width {
|
display: inline-block;
|
width: 23px;
|
}
|
}
|
.right {
|
width: 160px;
|
}
|
.devide {
|
width: 10px;
|
height: 1px;
|
background: #a9a9a9;
|
margin: 14px 3px;
|
}
|
}
|
.btns {
|
margin-top: 10px;
|
}
|
.el-form-item {
|
margin-bottom: 12px;
|
}
|
.el-form-item__content {
|
font-size: 12px;
|
line-height: 30px;
|
}
|
.el-form-item__error {
|
left: 70px;
|
top: 94%;
|
}
|
}
|
}
|
}
|
.panorama-info {
|
width: 1070px;
|
// margin: 0 auto;
|
margin-left: 100px;
|
display: flex;
|
padding-bottom: 30px;
|
.img-wrap {
|
.img-area {
|
position: relative;
|
}
|
.label {
|
position: absolute;
|
border-radius: 50%;
|
.text {
|
color: aqua;
|
font-size: 14px;
|
position: absolute;
|
bottom: 15px;
|
}
|
}
|
}
|
}
|
.user-upload {
|
margin: auto;
|
padding: 50px;
|
display: flex;
|
.info {
|
margin-left: 20px;
|
margin-top: 20px;
|
text-align: left;
|
font-size: 15px;
|
.input-area {
|
width: 300px;
|
label {
|
width: 80px;
|
color: rgba(39, 68, 111, 0.67);
|
}
|
> div {
|
display: flex;
|
align-items: center;
|
height: 40px;
|
}
|
}
|
.pos {
|
margin-top: 10px;
|
text-align: left;
|
color: rgba(39, 68, 111, 0.67);
|
b {
|
font-style: italic;
|
}
|
//color: #4966b7
|
}
|
}
|
.upload-demo,
|
.el-upload {
|
height: 100%;
|
width: 100%;
|
margin: 0 auto;
|
}
|
.upload-demo .el-upload__input {
|
visibility: hidden;
|
}
|
.upload-demo .el-upload-dragger {
|
width: 100%;
|
height: 90%;
|
width: 962px;
|
height: 542px;
|
margin: 20px 0 0;
|
background: transparent;
|
/* border: none; */
|
//position: relative;
|
overflow: visible;
|
border: none;
|
}
|
.upload-demo .el-upload__text {
|
position: absolute;
|
top: -24px;
|
left: 50%;
|
margin-left: -91px;
|
}
|
.upload-demo .preview {
|
object-fit: contain;
|
}
|
}
|
}
|
</style>
|