实战可行,vue3+vite+ts实现
实现电子地图,左侧列表可拖拽绑定
地图可绑定点设备坐标
安装
npm install konva
插件引入
import Konva from 'konva'
import Konva from 'konva'
import { getImgUrl } from '@/utils'
export class konvaManager {
public stage: any
public layer: any
private readonly width: number = window.innerWidth - 400
init(canvas: any) {
const stage = new Konva.Stage({
container: canvas,
height: 700,
width: this.width
})
const layer = new Konva.Layer()
stage.add(layer)
this.stage = stage
this.layer = layer
// this.sacleFun()
}
// 当成一个节点,绑定背景图
initBgImg(url: string, y: number = 150) {
const bgImg = new Image()
bgImg.src = getImgUrl(url)
bgImg.onload = () => {
const kImage = new Konva.Image({
image: bgImg,
x: 50,
y: y,
width: bgImg.width,
height: bgImg.height
})
this.layer.add(kImage)
}
}
// 如果允许点拖拽编辑绑定,drag传true
// img为项目需要,不同设备对应不同图标
initDevice(device: any, img: string = '', drag: boolean = false) {
const pointImg = new Image()
pointImg.src = getImgUrl('info/map/point.png') // 图片地址
pointImg.onload = () => {
const kImage = new Konva.Image({
draggable: drag,
image: pointImg,
x: device.x,
y: device.y,
width: pointImg.width,
height: pointImg.height
})
kImage.on('dblclick ', (e) => {
console.log('============,dblclick ', e, this.stage)
console.log('===双击保存,dblclick ', e.target.attrs.x, e.target.attrs.y)
})
this.layer.add(kImage)
}
// 如果show为true,可添加弹窗等点击事件响应
if (device.show) {
this.openDialog(device.fn, device)
}
if (img) {
const pointImg = new Image()
pointImg.src = getImgUrl(img)
pointImg.onload = () => {
const kImage = new Konva.Image({
draggable: drag,
image: pointImg,
x: device.x - 10,
y: device.y - 50,
width: pointImg.width,
height: pointImg.height
})
kImage.on('click ', (e) => {
device.fn(device.id)
})
kImage.on('mouseenter', () => {
this.stage.container().style.cursor = 'pointer'
})
kImage.on('mouseleave', () => {
this.stage.container().style.cursor = 'default'
})
this.layer.add(kImage)
}
}
}
openDialog(fun, device) {
const obj = {
...device,
location: {
left: device!.x - 20 + 'px',
top: device!.y - device.property.length * 30 + 'px'
}
}
fun(obj)
}
// 放大缩小时点不会偏移
sacleFun() {
this.stage.on('wheel', (e: any) => {
const max = 4 // 放大最大的比例
const min = 0.5 // 缩小最小的比例
const step = 0.03 // 每次缩放的比例
const x = e.evt.offsetX
const y = e.evt.offsetY
const offsetX =
((x - this.layer.offsetX()) * this.layer.scaleX()) / (this.layer.scaleX() - step) -
(x - this.layer.offsetX())
const offsetY =
((y - this.layer.offsetY()) * this.layer.scaleY()) / (this.layer.scaleY() - step) -
(y - this.layer.offsetY())
if (e.evt.wheelDelta) {
if (e.evt.wheelDelta > 0) {
// 放大
if (this.layer.scaleX() < max && this.layer.scaleY() < max) {
this.layer.scaleX(this.layer.scaleX() + step)
this.layer.scaleY(this.layer.scaleY() + step)
this.layer.move({ x: -offsetX, y: -offsetY }) // 跟随鼠标偏移位置
}
} else {
// 缩小
if (this.layer.scaleX() > min && this.layer.scaleY() > min) {
this.layer.scaleX(this.layer.scaleX() - step)
this.layer.scaleY(this.layer.scaleY() - step)
this.layer.move({ x: offsetX, y: offsetY }) // 跟随鼠标偏移位置
}
}
}
})
}
}
绑定设备时传入的图标,可点击弹窗详情
初始化
<div class="cavans" id="entranceCavans"></div>
<div class="absolute right-20px top-60px">
<draggable @drag="dragEvent" />
</div>
import { konvaManager } from './init'
const konvaCanvas = new konvaManager()
import draggable from '@/views/info/konva/draggable.vue'
/** 初始化 */
onMounted(() => {
konvaCanvas.init('entranceCavans')
konvaCanvas.initBgImg('info/map1.png')
// videoClick 为设备点绑定的点击事件
setTimeout(() => {
konvaCanvas.initDevice({ id: '1', x: 600, y: 360, fn: videoClick }, 'info/map/access.png')
konvaCanvas.initDevice({ id: '2', x: 250, y: 400, fn: videoClick }, 'info/map/access.png')
konvaCanvas.initDevice({ id: '3', x: 1108, y: 443, fn: videoClick }, 'info/map/access.png')
})
})
// 拖拽结束时点的坐标,不知道是不是绑定底图设置的宽高原因,导致拖拽后实际点的坐标放在konva上会有偏移
// 给减去一定值才会是鼠标抬起后再画布上的位置
const dragEvent = (evt) => {
konvaCanvas.initDevice(
{ id: '3', x: evt.originalEvent.x - 310, y: evt.originalEvent.y - 180 },
'',
true
)
}
绑定 拖拽节点
需要引入vuedraggable
npm i -S vuedraggable
<!-- 拖拽列表 -->
<template>
<div class="border border-[#1F62B7] border-solid">
<draggable
v-model="myArray"
@start="onStart"
@end="onEnd"
:sort="false"
item-key="id"
draggable=".item"
handle=".mover"
>
<template #item="{ element }">
<div class="item flex items-center p-2" :class="{ 'cursor-not-allowed': element.x }">
<img
src="@/assets/imgs/info/map/point.png"
class="w-15px"
:class="{ mover: !element.x }"
/>
<span class="ml-10px text-14px text-[#fff]">{{ element.name }}</span>
</div>
</template>
</draggable>
</div>
</template>
<script setup lang="ts">
import draggable from 'vuedraggable'
// 如果有坐标,列表不允许拖拽
const myArray = ref([
{ name: '设备1', id: '1', x: 0 },
{ name: '设备2', id: '2', x: 100 },
{ name: '设备3', id: '3', x: 0 }
])
//开始拖拽事件
const onStart = () => {
console.log('开始拖拽事件')
}
//拖拽结束事件
const onEnd = (evt) => {
console.log('拖拽结束事件', evt)
emit('drag', evt)
}
const emit = defineEmits<{
(e: 'drag', obj: object)
}>()
</script>