device-marker-canvas
v1.0.1
Published
A Vue.js component for marking devices on a canvas
Maintainers
Readme
DeviceMarkerCanvas 组件使用说明
本组件是一个vue2组件,用于在平面图上进行设备标记与查看,支持拖拽添加、选中拖动、等比自适应、DPR高清绘制、查看/编辑两种模式切换。
特性概览
- 等比自适应:图片以 contain 方式适配画布,始终完整展示并居中。
- 高清绘制:自动根据
devicePixelRatio放大画布像素,保证清晰度。 - 拖拽交互:从右侧设备列表拖入画布以添加标记;编辑模式下可选中拖动标记。
- 防重复添加:同一设备仅允许添加一次(可按需扩展)。
- 事件丰富:图片加载、画布缩放、标记新增/移动等事件上报,方便与业务联动。
- 响应式:监听容器尺寸与窗口变化,实时重算画布尺寸与图片适配。
## 基础用法示例
```vue
<template>
<div class="page">
<DeviceMarkerCanvas
:backageImageSrc="planUrl"
:existingMarkers="lockedMarkers"
:deviceList="devices"
:mode="pageMode"
:moveIconFillColor="'#e6a23c'"
:moveEndIconFillColor="'#409eff'"
:disabledIconFillColor="'#67c23a'"
@image-loaded="onImageLoaded"
@canvas-resized="onCanvasResized"
@marker-added="onMarkerAdded"
@marker-moved="onMarkerMoved"
/>
</div>
</template>
<script>
export default {
data() {
return {
pageMode: 'edit', // 'edit' | 'view'
planUrl: 'https://picsum.photos/800/500',
// 只读标记(不可编辑,绿色展示)
lockedMarkers: [
{ id: 'lk1', deviceId: 'D-100', deviceName: '摄像头', x: 1200, y: 800 },
],
// 可拖拽设备列表(右侧)
devices: [
{ id: 'D-101', name: '烟感', icon: 'icon-smoke' },
{ id: 'D-102', name: '摄像头', icon: 'icon-camera' },
],
};
},
methods: {
onImageLoaded(meta) {
console.log('图片尺寸:', meta); // { naturalWidth, naturalHeight }
},
onCanvasResized(meta) {
console.log('画布尺寸:', meta); // { width, height, dpr }
},
onMarkerAdded(marker) {
console.log('新增标记:', marker);
// 可在此将标记保存到后端
},
onMarkerMoved(marker /*, allMarkers*/ ) {
console.log('移动标记:', marker);
// 可在此持久化保存移动后的坐标
},
},
};
</script>
<style scoped>
.page { height: 100vh; display: flex; }
</style>属性(Props)
backageImageSrc(String, required)- 平面图图片地址。
existingMarkers(Array, default: [])- 已存在的只读标记列表(不可编辑,默认绿色)。
deviceList(Array, default: [])- 可拖拽设备列表,作为右侧设备面板的数据源。
mode(String, default: 'edit', oneOf: 'edit' | 'view')- 工作模式:
edit可编辑、view查看。
- 工作模式:
moveIconFillColor(String, default: '#e6a23c')- 选中/移动中的标记填充色。
moveEndIconFillColor(String, default: '#409eff')- 非选中(新增集合中的)标记填充色。
disabledIconFillColor(String, default: '#67c23a')- 只读标记(existingMarkers)填充色。
事件(Emits)
image-loaded->{ naturalWidth, naturalHeight }- 背景图片加载完成时触发。
canvas-resized->{ width, height, dpr }- 画布因容器变化重算后触发。
marker-added->marker- 从设备列表拖入画布后触发,返回新增标记对象。
marker-moved->marker[, allMarkers]- 编辑模式下拖动标记释放鼠标后触发。
数据结构
- 设备(deviceList item)
interface DeviceItem {
id: string; // 设备唯一ID
name: string; // 设备名称(用于标注)
icon?: string; // 可选,右侧面板的图标class
}- 标记(existingMarkers 与 新增标记)
interface MarkerItem {
id: string; // 标记ID(组件对新增标记会生成随机ID)
deviceId: string; // 设备ID(来自 deviceList.id)
deviceName: string; // 设备名称(来自 deviceList.name)
x: number; // 世界坐标(以图片原始像素为单位)
y: number; // 世界坐标(以图片原始像素为单位)
}交互说明
- 拖拽新增
- 从右侧设备面板拖动某设备图标到画布放开,即在鼠标所指位置添加此设备标记。
- 同一设备默认仅允许添加一次(内部用
addedDeviceIds约束)。
- 选中/拖动(编辑模式)
- 在画布上点击已有“新增标记”可选中;按住拖动以移动位置;释放触发
marker-moved。 - 只读标记
existingMarkers不可编辑,仅展示。
- 在画布上点击已有“新增标记”可选中;按住拖动以移动位置;释放触发
自适应与坐标体系
- 画布尺寸:
- 组件实时读取容器尺寸,设置 CSS 尺寸与像素尺寸(乘以 DPR)。
- 图片等比适配:
fitImage()使用 contain 策略计算scale/offsetX/offsetY,使图片完整且居中显示。
- 坐标转换:
- 世界 → 屏幕:
screen = world * scale + offset。 - 屏幕 → 世界:
world = (screen - offset) / scale。 - 组件内部所有存储坐标为“世界坐标”(基于图片原始尺寸),因此不受缩放影响,便于持久化。
- 世界 → 屏幕:
样式与布局
- 组件根容器:
device-marker-container,包含:- 左侧/顶部:
floor-plan-section(画布所在区域,flex居中)。 - 右侧/底部:
device-list-section(设备面板,仅在mode !== 'view'时渲染)。
- 左侧/顶部:
- 查看模式
view:根容器会添加is-view类,移除边框与外间隙,确保画布居中且铺满可用空间。
效果图
性能建议
- 大量标记时可考虑:
- 仅在数据变更时重绘;
- 将只读标记缓存到离屏 canvas 再一次性绘制;
- 降低重绘频率(resize 防抖)。
常见问题
- 平面图不铺满或不居中?
- 请确保外层容器可伸缩,组件已在
view模式下移除外间隙与右边框,同时在mode切换时会重新计算画布尺寸与图片适配。
- 拖入无效或设备列表不显示?
- 请确保
mode === 'edit';并检查外层是否给了足够高度与可见区域;设备列表由deviceList决定。
- 为什么坐标值很大?
- 坐标为图片原始像素(世界坐标),便于缩放不失真、存储一致。渲染时会转换到屏幕坐标。
二次开发指引
- 更换图标形态:修改
drawMarker或将其拆分为图标绘制与标签绘制两个方法; - 自定义命中半径:调整
hitTest中半径r; - 允许重复添加:去掉
addedDeviceIds约束或基于设备类型而非ID限制。
如需更多示例或联调帮助,可在页面中监听上述事件并接入后端保存逻辑。
