wft-vue3-picture-marker
v1.0.15
Published
一个基于 Vue 3.4+、element-plus 和 D3.js 的**通用图片标记组件**,同时提供**插件式安装**支持。 在任意静态图片(如平面图、示意图、游戏地图等)上轻松添加、编辑和交互式操作标记点。
Maintainers
Readme
wft-vue3-picture-marker
一个基于 Vue 3.4+、element-plus 和 D3.js 的通用图片标记组件,同时提供插件式安装支持。 在任意静态图片(如平面图、示意图、游戏地图等)上轻松添加、编辑和交互式操作标记点。
介绍
支持流畅的缩放与拖拽,标记点始终与图片坐标绑定,并在缩放时保持视觉大小不变 —— 非常适合用于标注、可视化配置等场景。 如果您有此类需求,那么使用此组件将 greatly help you.
核心特性
- 无需地理信息,适用于任何图片(支持本地图片、https/http链接、base64图片、Blob/File对象等)
- 支持拖拽、缩放(具备响应式能力,可动态设置是否可拖拽、缩放)
- 支持添加、编辑、删除标记点
- 支持自定义标记点样式
- 内置右键菜单(编辑、删除)
- 支持自定义右键面板内容、样式
- 内置右键编辑弹窗表单
- 支持自定义右键编辑表单弹窗内容
- 支持右键自定义逻辑
- 内置点击标记点详情弹窗
- 支持自定义点击标记点详情弹窗内容、样式
- 支持自定义点击标记点逻辑
- 支持自定义工具栏
- 提供完善的属性参数、方法、事件和插槽,方便进行二次开发
- ......
快速使用
安装
pnpm add element-plus d3 wft-vue3-picture-marker -S
pnpm add @types/d3 -D引入
全局引入
- main.ts
import WftVue3PictureMarker from 'wft-vue3-picture-marker'
import 'wft-vue3-picture-marker/dist/wft-vue3-picture-marker.css'
createApp(App).use(WftVue3PictureMarker)局部引入
- test.vue
import { WftVue3PictureMarker } from 'wft-vue3-picture-marker'
import 'wft-vue3-picture-marker/dist/wft-vue3-picture-marker.css'使用示例
<template>
<div class="picture-marker-container">
<WftVue3PictureMarker
ref="WftVue3PictureMarkerRef"
image-src="https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg"
v-model="markers"
width="100%"
height="100%"
:zoomable
:pannable
:min-scale="1"
:max-scale="7"
:circle
:rect
:triangle
:default-scale="1"
:default-translate="[0,0]"
:marker-type
:custom-click-popup="true"
:custom-contextmenu="false"
:custom-contextmenu-edit="true"
@base-click="baseClick"
@marker-click="markerClick"
@marker-edit="markerEdit"
@marker-delete="markerDelete"
>
<template #extra>
<el-button @click="getCurrentZoom" class="wft-toolbar-btn">获取缩放级别</el-button>
<el-button @click="zoomableChange" class="wft-toolbar-btn">{{ zoomable ? '禁止缩放' : '开启缩放' }}</el-button>
<el-button @click="pannableChange" class="wft-toolbar-btn">{{ pannable ? '禁止拖拽' : '开启拖拽' }}</el-button>
<el-button @click="markerType = 'circle'" class="wft-toolbar-btn">标记圆形</el-button>
<el-button @click="markerType = 'rect'" class="wft-toolbar-btn">标记矩形</el-button>
<el-button @click="markerType = 'triangle'" class="wft-toolbar-btn">标记三角形</el-button>
</template>
</WftVue3PictureMarker>
</div>
</template>
<script setup lang='ts'>
import { WftVue3PictureMarker, type IMarkerItem, type TMarkerType } from 'wft-vue3-picture-marker'
import 'wft-vue3-picture-marker/dist/wft-vue3-picture-marker.css'
import { ref } from 'vue'
import { getUuid } from 'wft-utils'
const WftVue3PictureMarkerRef = ref<InstanceType<typeof WftVue3PictureMarker>>()
const markers = ref<IMarkerItem[]>([])
const zoomable = ref(true)
const pannable = ref(true)
const markerType = ref<TMarkerType>('circle')
// 圆形标记的初始配置(优先级低于IMarkerItem)
const circle = ref({
r: 7,
strokeWidth: 2,
stroke: '#FFFFFF',
fill: '#376EFF',
})
// 矩形标记的初始配置(优先级低于IMarkerItem)
const rect = ref({
width: 12,
height: 12,
strokeWidth: 2,
stroke: '#FFFFFF',
fill: '#376EFF',
})
// 三角形标记的初始配置(优先级低于IMarkerItem)
const triangle = ref({
size: 8,
strokeWidth: 2,
stroke: '#FFFFFF',
fill: '#376EFF',
})
// 点击底图添加标记点
const baseClick = ({ ratioX, ratioY, g: _ }: any) => {
/** 如果要高度定制每个标记点的形状、样式,可以在push的时候,单独指定markerType以及改类型对应的样式,参考IMarkerItem类型 */
/** 方式1 */
WftVue3PictureMarkerRef.value?.push({ id: getUuid(), ratioX, ratioY })
/** 方式2 */
// markers.value.push({ id: getUuid(), ratioX, ratioY })
}
// 点击标记点
const markerClick = ({ event, id }: any) => {
console.log(event, id, '点击')
// 获取当前的标记点selection
const selection = WftVue3PictureMarkerRef.value?.getMarker(id)
console.log(selection, '当前标记点selection--->')
}
// 右键编辑
const markerEdit = (id: string) => {
console.log(id, '右键编辑--->')
}
// 右键删除
const markerDelete = (id: string) => {
console.log(id, '右键删除--->')
/** 方式1 */
WftVue3PictureMarkerRef.value?.remove(id)
/** 方式2 */
// markers.value = markers.value.filter(item => item.id != id)
}
// 获取当前缩放级别
const getCurrentZoom = () => {
const zoom = WftVue3PictureMarkerRef.value?.getZoom()
console.log(zoom)
}
// 是否开启缩放动态设置
const zoomableChange = () => {
zoomable.value = !zoomable.value
}
// 是否开启拖拽动态设置
const pannableChange = () => {
pannable.value = !pannable.value
}
</script>
<style scoped>
.picture-marker-container {
width: 100%;
height: 100%;
}
</style>API介绍
props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| modelValue | 双向绑定的标记点数据列表(用于 v-model) | IMarkerItem[] | []|
| imageSrc | 图片源,支持静态导入、HTTP/HTTPS URL、Blob 等(响应式) | string \| Blob | — |
| width | 容器宽度(响应式) | 'number' \| 'string' | 100% |
| height | 容器高度(响应式) | 'number' \| 'string' | 100% |
| zoomable | 是否可缩放(响应式) | boolean | true |
| pannable | 是否可拖拽(响应式) | boolean | true |
| minScale | 最小缩放比例 | number | 1 |
| maxScale | 最大缩放比例 | number | 7 |
| defaultScale | 默认缩放比例 | number | 1 |
| defaultTranslate | 默认平移位置 [x, y] | [number, number] | [0, 0] |
| markerType | 默认标记点类型 | 'circle' \| 'rect' \| 'triangle' | 'circle' |
| circle | 圆形标记默认样式配置 | ICircle | { r: 7, strokeWidth: 2, stroke: '#FFFFFF', fill: '#376EFF' } |
| rect | 矩形标记默认样式配置 | IRect | { width: 12, height: 12, strokeWidth: 2, stroke: '#FFFFFF', fill: '#376EFF' } |
| triangle | 三角形标记默认样式配置 | ITriangle | { size: 8, strokeWidth: 2, stroke: '#FFFFFF', fill: '#376EFF' } |
| idKey | 标记点数据中唯一标识字段名 | string | 'id' |
| ratioXKey | 标记点数据中 X 坐标字段名(归一化比例) | string | 'ratioX' |
| ratioYKey | 标记点数据中 Y 坐标字段名(归一化比例) | string | 'ratioY' |
| customClickPopup | 是否自定义点击标记点弹窗逻辑 | boolean | false |
| clickPopupStyle | 点击弹窗的 CSS 样式(当 customClickPopup=false 时生效) | CSSProperties | {} |
| customContextmenu | 是否自定义右键菜单逻辑 | boolean | false |
| contextmenuPopupStyle | 右键菜单面板样式(当 customContextmenu=false 时生效) | CSSProperties | {} |
| customContextmenuEdit | 是否自定义右键编辑菜单逻辑(当 customContextmenu=false 时生效) | boolean | false |
| contextmenuEditPopupTitle | 右键编辑面板标题 | string | '信息维护' |
| contextmenuEditPopupWidth | 右键编辑面板宽度 | string | '40%' |
| formSchema | 编辑表单结构(当 customContextmenu=false 且 customContextmenuEdit=false 时生效) | IFormItem[] | [] |
| formData | 编辑表单初始数据 | Record<string, any> | {} |
| detailSchema | 详情弹窗表单结构(当 customClickPopup=false 时生效) | Array<Omit<IFormItem, 'required'>> | [] |
| detailData | 详情弹窗表单数据 | Record<string, any> | {} |
events
| 事件名 | 说明 | 参数 |
| --- | --- | --- |
| base-click | 点击地图触发 | { ratioX: number, ratioY: number, g: any } |
| marker-click | 点击标记点触发 | { event: Event, id: string } |
| marker-contextmenu | 右键点击标记点触发 | { event: Event, id: string } |
| marker-delete | 点击右键面板删除触发 | id: string |
| marker-edit | 点击右键面板编辑触发 | id: string |
| info-submit | 提交表单触发 | data: { [k: typeof IFormItem[field]]: string } |
methods
| 方法 | 说明 | 参数 |
| --- | --- | --- |
| push | 添加单个标记点 | marker: IMarkerItem |
| remove | 删除指定 ID 的标记点 | id: string |
| update | 更新单个标记点(根据 id 匹配) | marker: IMarkerItem |
| batchInsert | 批量添加多个标记点 | list: IMarkerItem[] |
| batchRemove | 批量删除多个标记点 | ids: string[] |
| batchUpdate | 批量更新多个标记点(根据 id 匹配) | list: IMarkerItem[] |
| clear | 清空所有标记点 | — |
| getMarker | 获取指定 ID 的标记点数据 | id: string |
| getZoom | 获取当前地图的缩放比例 | — |
| getSVG | 获取当前地图的 SVG 容器元素 | — |
slots
| 插槽名 | 说明 | 参数作用域 |
| --- | --- | --- |
| extra | 右上角工具栏自定义内容 | — |
| contextmenu-popup-content | 右键面板自定义内容(当 customContextmenu 为 false 时生效) | { id: string } |
| click-popup-content | 点击标记点弹窗自定义内容(当 customClickPopup 为 false 时生效) | { id: string } |
| contextmenu-edit-popup-content | 右键编辑弹窗主体自定义内容(当 customContextmenu 和 customContextmenuEdit 均为 false 时生效) | { id: string } |
| contextmenu-edit-popup-footer | 右键编辑弹窗底部区域自定义内容(当 customContextmenu 和 customContextmenuEdit 均为 false 时生效) | { id: string } |
类型定义
type TMarkerType = 'circle' | 'rect' | 'triangle';
interface ICircle { // 圆形标记点参数(markerType=circle时生效)
r?: number; // 半径
strokeWidth?: number; // 边框宽度
stroke?: string; // 边框颜色
fill?: string; // 填充颜色
}
interface IRect { // 矩形标记点参数(markerType=rect时生效)
width?: number; // 宽度
height?: number; // 高度
strokeWidth?: number; // 边框宽度
stroke?: string; // 边框颜色
fill?: string; // 填充颜色
}
interface ITriangle { // 三角形标记点参数(markerType=triangle时生效)
size?: number; // 大小
strokeWidth?: number; // 边框宽度
stroke?: string; // 边框颜色
fill?: string; // 填充颜色
}
interface IMarkerItem {
id: string; // 标记点唯一标识
ratioX: number; // 圆心X位置
ratioY: number; // 圆心Y位置
markerType?: TMarkerType;
circle?: ICircle;
triangle?: ITriangle;
rect?: IRect;
[k: string]: any;
}
interface IFormItem {
label?: string; // 标题
labelWidth?: number; // 标题宽度
field: string; // 字段名
required?: boolean; // 是否必填
defaultValue?: any; // 默认值
}
interface IProps {
// modelValue: IMarkerItem[]; // 标记点列表
imageSrc: string | Blob;
width?: number | string; // 容器宽度
height?: number | string; // 容器高度
zoomable?: boolean; // 是否可缩放
pannable?: boolean; // 是否可拖拽
minScale?: number; // 最小缩放比例
maxScale?: number; // 最大缩放比例
defaultScale?: number; // 默认缩放比例
defaultTranslate?: [number, number]; // 默认移动位置
markerType?: TMarkerType; // 标记点类型
circle?: ICircle;
triangle?: ITriangle;
rect?: IRect;
idKey?: string; // 标记点列表list中的id字段
ratioXKey?: string; // 标记点列表list中的ratioX字段
ratioYKey?: string; // 标记点列表list中的ratioY字段
customClickPopup?: Boolean; // 自定义点击标记点逻辑
clickPopupStyle?: CSSProperties; // 详情弹窗样式(customClickPopup为false时生效)
customContextmenu?: Boolean; // 自定义右键菜单逻辑
contextmenuPopupStyle?: CSSProperties; // 右键面板样式(customContextmenu为false时生效)
customContextmenuEdit?: Boolean; // 自定义右键编辑菜单逻辑(customContextmenu为false时生效)
contextmenuEditPopupTitle?: string; // 右键编辑面板标题(customContextmenu为false时生效)
contextmenuEditPopupWidth?: string; // 右键编辑面板宽度(customContextmenu为false时生效)
formSchema?: IFormItem[]; // 编辑表单结构(customContextmenu为false且customContextmenuEdit为false时生效)
formData?: Record<string, any>; // 编辑表单数据(customContextmenu为false且customContextmenuEdit为false时生效)
detailSchema?: Array<Omit<IFormItem, 'required'>>; // 详情表单结构(customClickPopup为false时生效)
detailData?: Record<string, any>; // 详情表单数据(customClickPopup为false时生效)
}作者
- 姓名:wangfutai
- 邮箱:[email protected]
- CSDN:@wangfutai
- Gitee:@wangfutai
- GitHub:@wangfutai
欢迎提交 Issue 或 Pull Request!如有合作或问题,可通过邮箱联系。
