这个组件是VUE+vue-drag-resize +canvas 实现的点位添加拖拽,下面是整体效果:
首先有一个上传页面上传背景
代码如下:
<!-- 上传组件 -->
<el-upload class="upload-demo" drag action="https://jsonplaceholder.typicode.com/posts/" multiple
v-show="isUpload" :on-success="uploadSuccess" :on-error="uploadError">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
图片上传成功后显示canvas画布
HTML代码如下:
<!-- 生成画布模块 -->
<div class="canvas-box" v-show="this.canvasinit && !isUpload">
<!-- 画布 -->
<canvas ref="canvas" :width="canvasWidth" :height="canvasHeight" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp"></canvas>
<!-- 点位 -->
<vue-drag-resize v-for="(item,index) in canvasSensorImg" :isActive="true" :isResizable="false" :parentLimitation="true"
:isDraggable="!isDelete && !isEditor" :parentW="parent.w" :parentH="parent.h" @dragstop="dragstop" :x="item.x"
:y="item.y" :z="999" :w="item.width" :h="item.height" class="drag2">
<!-- <img :src="item.url" alt="" srcset=""> -->
<div class="text"> V{{ index + 1 }}</div>
<img src="../../../../public/img/disposition/delete.png" alt="" srcset="" class="delete" @click="goDelete"
v-show="isDelete">
<img src="../../../../public/img/disposition/editor.png" alt="" srcset="" class="editor" @click="goEditor"
v-show="isEditor">
</vue-drag-resize>
<!-- 卡片 -->
<vue-drag-resize v-for="item in tagList" :isActive="true" :isResizable="false" :parentLimitation="true"
:isDraggable="!isDelete && !isEditor" :parentW="parent.w" :parentH="parent.h" @dragstop="dragstop" :x="item.x"
:y="item.y" :z="999" :w="200" :h="80" class="drag">
<img src="../../../../public/img/disposition/shui.png" alt="" srcset="">
<ul>
<li>{{item.name}}</li>
<li>{{item.temperature}}<span></span></li>
</ul>
<img src="../../../../public/img/disposition/delete.png" alt="" srcset="" class="delete" @click="goDelete"
v-show="isDelete">
<img src="../../../../public/img/disposition/editor.png" alt="" srcset="" class="editor" @click="goEditor"
v-show="isEditor">
</vue-drag-resize>
</div>
// 创建画布
init() {
// 找到画布标签
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext("2d");
console.log('创建画布')
// 创建背景,图标,移动图标
this.loadBgImg();
// 刷新画布
},
loadBgImg() {
let img = new Image();
let bgImg = this.backgroundImg;
img.src = bgImg.url;
img.onload = () => {
this.canvasWidth = img.width;
this.canvasHeight = img.height;
this.ctx.clearRect(0, 0, img.width, img.height);
this.ctx.drawImage(img, bgImg.x, bgImg.y, img.width, img.height);
this.parent.w = img.width
this.parent.h = img.height
document.querySelector('.canvas-box').style.width = img.width + 'px';
document.querySelector('.canvas-box').style.height = img.height + 'px';
};
},
完整代码如下
<template>
<div element-loading-spinner="el-icon-loading" element-loading-text="加载中"
v-loading="itemLoading">
<div style="margin-left: -5px">
<tree-breadcrumb :list="treeList" @onClickItem="onClickItem"></tree-breadcrumb>
</div>
<el-select v-model="value" placeholder="请选择" class="equipmentSelect">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<div class="FeatureList" v-show="!isUpload">
<el-button type="primary" plain size="mini" @click="Editor">{{isEditor?'取消编辑':'编辑'}}</el-button>
<el-button type="primary" plain size="mini" @click="updateBackground" v-show="isEditor">更新底图</el-button>
<el-button type="danger" plain size="mini" @click="Delete">{{isDelete?'取消删除':'删除'}}</el-button>
<el-button type="danger" plain size="mini" @click="deleteBackground" v-show="isDelete">删除底图</el-button>
</div>
<div class="app-container">
<div class="clear"></div>
<div class="grid-right">
<!-- 上传组件 -->
<el-upload class="upload-demo" drag action="https://jsonplaceholder.typicode.com/posts/" multiple
v-show="isUpload" :on-success="uploadSuccess" :on-error="uploadError">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<!-- 生成画布模块 -->
<div class="canvas-box" v-show="this.canvasinit && !isUpload">
<!-- 画布 -->
<canvas ref="canvas" :width="canvasWidth" :height="canvasHeight" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp"></canvas>
<!-- 点位 -->
<vue-drag-resize v-for="(item,index) in canvasSensorImg" :isActive="true" :isResizable="false" :parentLimitation="true"
:isDraggable="!isDelete && !isEditor" :parentW="parent.w" :parentH="parent.h" @dragstop="dragstop" :x="item.x"
:y="item.y" :z="999" :w="item.width" :h="item.height" class="drag2">
<!-- <img :src="item.url" alt="" srcset=""> -->
<div class="text"> V{{ index + 1 }}</div>
<img src="../../../../public/img/disposition/delete.png" alt="" srcset="" class="delete" @click="goDelete"
v-show="isDelete">
<img src="../../../../public/img/disposition/editor.png" alt="" srcset="" class="editor" @click="goEditor"
v-show="isEditor">
</vue-drag-resize>
<!-- 卡片 -->
<vue-drag-resize v-for="item in tagList" :isActive="true" :isResizable="false" :parentLimitation="true"
:isDraggable="!isDelete && !isEditor" :parentW="parent.w" :parentH="parent.h" @dragstop="dragstop" :x="item.x"
:y="item.y" :z="999" :w="200" :h="80" class="drag">
<img src="../../../../public/img/disposition/shui.png" alt="" srcset="">
<ul>
<li>{{item.name}}</li>
<li>{{item.temperature}}<span></span></li>
</ul>
<img src="../../../../public/img/disposition/delete.png" alt="" srcset="" class="delete" @click="goDelete"
v-show="isDelete">
<img src="../../../../public/img/disposition/editor.png" alt="" srcset="" class="editor" @click="goEditor"
v-show="isEditor">
</vue-drag-resize>
</div>
</div>
<!-- 添加监测点弹窗 -->
<el-dialog :title="dialogFormTitle" :visible.sync="dialogFormVisible" :modal-append-to-body='false'>
<el-form :model="form">
<el-form-item label="监测点" :label-width="formLabelWidth">
<el-select v-model="form.region1" placeholder="请选择监测点">
<el-option label="选项1" value="1"></el-option>
<el-option label="选项2" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="监测项" :label-width="formLabelWidth">
<el-select v-model="form.region2" placeholder="请选择监测项">
<el-option label="选项1" value="1"></el-option>
<el-option label="选项2" value="2"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
<!-- 更新背景 -->
<el-dialog title="更新背景" :visible.sync="isUpdateBackground" :modal-append-to-body='false'>
<el-upload class="upload-demo" drag action="https://jsonplaceholder.typicode.com/posts/" multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="isUpdateBackground = false">取 消</el-button>
<el-button type="primary" @click="isUpdateBackground = false">确 定</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import VueDragResize from 'vue-drag-resize';
import TreeBreadcrumb from '../../../components/tree-breadcrumb/main'
import Cookies from 'js-cookie'
const imgUrl = require('../../../../public/img/disposition/backgroundImg.png')
export default {
name: "ce_shi",
components: {
VueDragResize,
TreeBreadcrumb
},
props: {
deptList: {
type: Array,
default: []
}
},
watch: {
deptList: function (val) {
//刷新数据
this.treeList = val;
this.deptId = val[val.length - 1].id;
//调用刷新页面数据方法
// this.onLoad(this.page);
},
},
data() {
return {
treeList: [],
deptId:'',
options: [{
value: '1',
label: '减速机-01'
}, {
value: '2',
label: '减速机-02'
}, {
value: '3',
label: '减速机-03'
}],
value: '1',
// x显示标签添加弹窗
dialogFormVisible: false,
dialogFormTitle: '',
formLabelWidth: '120px',
isEditor: false,//是否开启编辑模式
isDelete: false,//是否开启删除模式
isUpload: true,
isUpdateBackground:false,//更新背景弹窗
form: {
region1: '',
region2: '',
},
parent: {
w: 1920,
h: 1080
},
//鼠标点击创建数据
pointData: {},
// 获取canvas标签
canvas: null,
// 创建画布
ctx: null,
// 画布大小
canvasWidth: 970,
canvasHeight: 500,
//定时器
intervalId: null,
//判断鼠标是否点击
isClick: false,
//记录需要移动的图片的开光
index: -1,
frameNumber: 20,
// 标签卡片数组
tagList: [{
x: 20,
y: 50,
name: 'OT-润滑油油温',
temperature: '46℃'
}],
backgroundImg: {
// url: "https://img2.baidu.com/it/u=2832413337,2216208892&fm=253&fmt=auto&app=138&f=JPEG?w=544&h=500",
url: imgUrl,
x: 0,
y: 0,
width: 970,
height: 500,
},
canvasSensorImg: [
{
channelId: 12,
height: 26,
url: "https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
width: 26,
x: 247,
y: 233,
}
],
// 图标数据
Icondata:
"https://img2.baidu.com/it/u=3823882177,3352315913&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",
// 画布开关
canvasinit: false,
itemLoading:true
};
},
created() {
},
mounted() {
if (Cookies.get('breadcrumb')) {
this.refreshItem();
} else {
this.itemLoading = true;
var timer = setInterval(() => {
if (Cookies.get('breadcrumb')) {
this.itemLoading = false;
clearInterval(timer);
this.refreshItem();
}
}, 2000)
}
this.canvasinit = true;
this.init();
this.itemLoading = false
},
methods: {
// 顶部面包屑
refreshItem() {
let list = JSON.parse(Cookies.get('breadcrumb'));
this.treeList = list;
this.deptId = list[list.length - 1].id;
},
onClickItem(id, label, index) {
this.treeList.splice(index + 1);
let data = {
list: this.treeList,
flag: 0
};
this.$emit("changeCrumbs", data);
},
// 更新背景
updateBackground() {
this.isUpdateBackground = true
},
deleteBackground(){
this.goDelete()
},
// 上传成功
uploadSuccess(e) {
this.isUpload = false
},
// 上传失败
uploadError() {
this.isUpload = false
},
// 拖动结束
dragstop(e) {
console.log(e)
},
// 开启/关闭编辑模式
Editor() {
this.isDelete = false
this.isEditor = !this.isEditor
},
// 开启/关闭删除模式
Delete() {
this.isEditor = false
this.isDelete = !this.isDelete
},
goDelete() {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
goEditor() {
this.dialogFormVisible = true
this.dialogFormTitle = '显示标签编辑'
},
// 定时器刷新画布
dataRefreh() {
if (this.intervalId != null) {
return;
}
this.intervalId = setInterval(() => {
this.loadBgImg();
}, this.frameNumber);
},
// 创建画布
init() {
// 找到画布标签
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext("2d");
console.log('创建画布')
// 创建背景,图标,移动图标
this.loadBgImg();
// 刷新画布
this.dataRefreh();
},
loadBgImg() {
let img = new Image();
let bgImg = this.backgroundImg;
img.src = bgImg.url;
img.onload = () => {
this.canvasWidth = img.width;
this.canvasHeight = img.height;
this.ctx.clearRect(0, 0, img.width, img.height);
this.ctx.drawImage(img, bgImg.x, bgImg.y, img.width, img.height);
this.parent.w = img.width
this.parent.h = img.height
document.querySelector('.canvas-box').style.width = img.width + 'px';
document.querySelector('.canvas-box').style.height = img.height + 'px';
};
},
//判断鼠标是否在图标范围内,并返回下标
isMouseInIcon(e, imgList) {
let x = e.offsetX;
let y = e.offsetY;
for (let i = 0; i < imgList.length; i++) {
let imgX = imgList[i].x;
let imgY = imgList[i].y;
let imgWidth = imgList[i].width;
let imgHeight = imgList[i].height;
if (
x > imgX &&
x < imgX + imgWidth &&
y > imgY &&
y < imgY + imgHeight
) {
return i;
}
}
return -1;
},
//鼠标点击触发事件
canvasMouseDown(e) {
// 获取鼠标位置
const { clientX, clientY } = event;
// 获取canvas 边界位置
const { top, left } = this.canvas.getBoundingClientRect();
// 计算鼠标在canvas 中的位置
const x = clientX - left;
const y = clientY - top;
// 之前选过值的
var data = {
x: x,
y: y
}
console.log("鼠标点击", e);
this.isClick = true;
this.showdialogVisible(data)
this.pointData = data
},
//鼠标抬起触发事件
canvasMouseUp(e) {
this.isClick = false;
},
handleClick(data) {
// 判断是否上传楼层图片
// 创建点位
let imgs = {};
imgs.url = this.Icondata;
imgs.x = data.x;
imgs.y = data.y;
imgs.width = 26;
imgs.height = 26;
// 加载点位图标
this.canvasSensorImg.push(imgs);
this.$message.success("创建成功");
let item = {}
item.x = data.x - 50
item.y = data.y - 80
item.name = 'OT-润滑油油温'
item.temperature = '46℃'
this.tagList.push(item);
},
showdialogVisible(data) {
if (this.isDelete || this.isEditor) {
return
}
this.dialogFormTitle = '显示标签添加'
this.dialogFormVisible = true
},
save() {
this.handleClick(this.pointData)
this.dialogFormVisible = false
}
},
beforeDestroy() {
clearInterval(this.intervalId);
this.intervalId = null;
},
};
</script>
<style lang="scss" scoped>
.app-container {
background: #fff;
width: 100%;
height: 100%;
position: relative;
/* float: left; */
overflow: scroll;
/* overflow: hidden; */
}
.item {
width: 100px;
height: 100px;
background: red;
}
.drag2 img {
width: 100%;
height: 100%;
cursor: pointer;
}
.drag {
background: url(../../../../public/img/disposition/bg.png) no-repeat;
background-size: 100% 100%;
cursor: pointer;
}
.drag.active:before,
.drag2.active:before {
outline: none;
}
.drag img {
width: 40px;
float: left;
margin-top: 10px;
margin-left: 20px;
}
ul {
width: 140px;
display: inline-block;
float: left;
margin: 0;
padding: 10px 10px 0 0;
box-sizing: border-box;
}
ul li {
list-style: none;
text-align: right;
font-weight: bold;
font-size: 14px;
margin-bottom: 5px;
}
ul li span {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 5px;
background-color: green;
margin-left: 5px;
}
.canvas-box {
margin: 0 auto;
position: relative;
}
.equipmentSelect {
position: absolute;
top: 60px;
right: 30px;
z-index: 1000;
}
.FeatureList {
padding: 0 20px;
float: left;
position: absolute;
top: 60px;
left: 20px;
z-index: 1000;
}
.drag2 img.delete,
.drag img.delete {
width: 30px;
height: 30px;
position: absolute;
top: -15px;
right: -15px;
z-index: 9999;
}
.drag img.delete{
top: -25px;
}
.drag2 img.editor,
.drag img.editor {
width: 18px;
height: 18px;
position: absolute;
top: -9px;
right: -8px;
z-index: 9999;
}
.upload-demo {
display: block;
width: 500px;
margin: 100px auto 100px;
}
.clear {
width: 100%;
clear: both;
}
.text{
width: 100%;
height: 100%;
text-align: center;
line-height: 26px;
color: #1479e1;
font-weight: bold;
font-size: 16px;
cursor: pointer;
}
</style>