@composy/plate-editor-vue
v0.0.5
Published
Vue 3 adapter for @composy/plate-editor-core
Maintainers
Readme
@composy/plate-editor-vue
@composy/plate-editor-vue 是 @composy/plate-editor-core 的 Vue 3 适配包,提供完整的 <PlateEditor /> 组件、Props、事件转发和模板 ref API。
Vue 包负责 Vue 生命周期和响应式入口;Core 负责 Canvas 编辑、图片加载、检测器、透视渲染、工具栏、预设、历史、状态和导出。需要框架无关 API 时请直接使用 Core 完整文档。
组件内部创建的是 Core PlateEditorStudio,因此 mode="dialog" 的编辑器弹窗和预设弹窗会挂载到 document.body 下的 portal,并使用高层级固定定位,不会被业务容器的 overflow、transform、isolation 或局部 stacking context 截断。
目录
- 安装与环境
- 产物、CSS 与导入
- 最小示例
- 完整 Vue 示例
- inline 与 dialog
- 自定义触发按钮和弹窗
- 保存、上传和导出
- Props 完整说明
- options:Core 参数透传
- toolbar:工具栏定制
- 全部组件事件
- ref 暴露 API
- 响应式更新边界
- 图片、模型、检测和预设
- 类型和数据结构
- 样式产物
- UMD 浏览器用法
- CommonJS 用法
- 示例和 Playground
- 构建和销毁
安装与环境
pnpm add vue @composy/plate-editor-vuevue 是 peer dependency,需要由应用提供。使用 Core 内置 ONNX 检测器时,再安装:
pnpm add onnxruntime-web如果应用只使用手动四角选区、自定义检测器或远程检测器,则不需要安装 onnxruntime-web。
组件运行在浏览器,需要 DOM、Canvas、Blob/File 和 URL API。服务端渲染时不要在服务端创建编辑器;在 Nuxt 等 SSR 应用中使用 client-only 包装,或仅在客户端挂载组件。
产物、CSS 与导入
普通 Vue 应用优先从包根导入,npm exports 会自动选择正确的运行时产物:
| 目录或文件 | 类型 | 用法 |
| --- | --- | --- |
| es/ | preserve-modules ESM JavaScript + 声明 | 包根 import 默认解析到这里,适合 Vite、Webpack、Rollup |
| esm/ | 独立 preserve-modules ESM JavaScript + 声明 | import ... from '@composy/plate-editor-vue/esm' |
| lib/ | preserve-modules CommonJS JavaScript + 声明 | require('@composy/plate-editor-vue') |
| dist/index.js | 未压缩 UMD JavaScript | 静态页面调试 |
| dist/index.min.js | 压缩 UMD JavaScript | CDN 或生产 <script> |
| dist/index.css | 未压缩 Vue 宿主样式 | 调试或开发环境 |
| dist/index.min.css | 压缩 Vue 宿主样式 | 生产环境 |
| es/style/index.css | ESM 样式副本 | 需要 preserve-modules 样式路径时使用 |
| lib/style/index.css | CommonJS 样式副本 | 需要 CommonJS 样式路径时使用 |
| *.d.ts | TypeScript 声明 | Props、事件、ref、模型和状态类型 |
包根 ESM 导入
import { PlateEditor } from '@composy/plate-editor-vue'
import '@composy/plate-editor-vue/index.css'显式使用 esm/
import {
PlateEditor,
BUILT_IN_PLATE_PRESETS,
} from '@composy/plate-editor-vue/esm'esm/ 现在包含真实的 .js 运行时模块,不是只有声明文件的目录。普通项目仍建议使用包根入口,以便未来版本调整构建目录时保持兼容。
CommonJS
const {
PlateEditor,
BUILT_IN_PLATE_PRESETS,
} = require('@composy/plate-editor-vue')CommonJS 适配层仍然需要 Vue 和浏览器 DOM,不能在没有 DOM 的 Node 进程中直接挂载组件。
Core 的 Canvas 和 Studio 样式由 Core 自动注入;Vue 样式主要负责 wrapper、trigger 和 host 的基础布局。生产环境可以导入压缩 CSS:
import '@composy/plate-editor-vue/index.min.css'也可以使用 preserve-modules 样式入口:
import '@composy/plate-editor-vue/style/index.css'最小示例
<script setup lang="ts">
import { ref } from 'vue'
import { PlateEditor } from '@composy/plate-editor-vue'
import '@composy/plate-editor-vue/index.css'
const image = ref<File>()
function onFileChange(event: Event) {
image.value = (event.target as HTMLInputElement).files?.[0]
}
</script>
<template>
<input type="file" accept="image/jpeg,image/png,image/webp" @change="onFileChange" />
<PlateEditor :src="image" :auto-detect="false" height="640px" />
</template>src 变化时,新值会调用 Core loadImage();变为 undefined 时调用 clearImage()。图片错误会触发 image-error,同时触发兼容别名 error。
完整 Vue 示例
下面的示例包含模型配置、预设、图片加载、检测、事件、组件 ref 和导出:
<script setup lang="ts">
import { ref } from 'vue'
import {
BUILT_IN_PLATE_PRESETS,
PlateEditor,
} from '@composy/plate-editor-vue'
import type {
Editor,
ModelOptions,
PlateEditorExpose,
} from '@composy/plate-editor-vue'
import '@composy/plate-editor-vue/index.css'
const editorRef = ref<PlateEditorExpose>()
const image = ref<File | string>()
const status = ref('等待图片')
const model: ModelOptions = {
url: '/models/license-plate-detector.onnx',
outputFormat: 'corners',
coordinateFormat: 'normalized',
preferredBackends: ['webgpu', 'wasm'],
}
function onImageChange(event: Event) {
image.value = (event.target as HTMLInputElement).files?.[0]
}
function onReady(editor: Editor) {
editor.setAddMode(true)
status.value = '编辑器已就绪'
}
async function detect() {
status.value = '识别中…'
const plates = await editorRef.value?.detectPlates({ precise: true })
status.value = `识别到 ${plates?.length ?? 0} 个车牌`
}
async function exportImage() {
const blob = await editorRef.value?.export({
quality: 0.8,
useOriginalResolution: true,
})
if (!blob) return
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'plate-edited.png'
link.click()
URL.revokeObjectURL(url)
}
</script>
<template>
<section class="plate-page">
<input type="file" accept="image/*" @change="onImageChange" />
<button type="button" @click="detect">开始识别</button>
<button type="button" @click="exportImage">导出 PNG</button>
<span>{{ status }}</span>
<PlateEditor
ref="editorRef"
:src="image"
:options="{
model,
presets: BUILT_IN_PLATE_PRESETS,
showBuiltInPresets: true,
defaultPresetId: 'cn-blue-standard',
}"
:auto-detect="false"
theme="auto"
@ready="onReady"
@image-loaded="payload => status = `${payload.width} × ${payload.height}`"
@detect-complete="payload => status = `完成:${payload.plates.length} 个候选`"
@error="error => status = error.message"
/>
</section>
</template>如果你的应用不需要自动识别,可以删除 model 和 @detect-complete,保留 auto-detect="false",使用手动选区即可。
inline 与 dialog
PlateEditor 通过 mode 支持两种完整交互:
直接展示编辑面板
这是默认模式,组件直接渲染工作区、工具栏以及工具栏下方的“保存图片”按钮:
<PlateEditor
:src="image"
mode="inline"
:options="{ presets: customPresets, showBuiltInPresets: false }"
:auto-detect="false"
theme="light"
/>选择图片后弹出对话框
dialog 模式初始只显示一个选择图片按钮。用户选中图片后,组件自动打开编辑对话框;对话框右下角的保存按钮会调用 Core 的 save():
<script setup lang="ts">
import { ref } from 'vue'
import { PlateEditor } from '@composy/plate-editor-vue'
import type { PlateEditorExpose } from '@composy/plate-editor-vue'
const editorRef = ref<PlateEditorExpose>()
const customPresets = [
{
id: 'gochi-white',
name: 'GOCHI 白牌',
style: {
contentMode: 'image',
image: { source: '/assets/gochi-logo.png', fit: 'contain' },
backgroundColor: '#ffffff',
text: '',
textColor: '#1f2933',
aspectRatio: 440 / 140,
mountingBolts: { enabled: true, size: 4.5, inset: 16 },
},
},
]
async function save() {
await editorRef.value?.save('vehicle-plate', {
type: 'image/png',
useOriginalResolution: true,
})
}
</script>
<template>
<PlateEditor
ref="editorRef"
mode="dialog"
:options="{
presets: customPresets,
showBuiltInPresets: false,
defaultPresetId: 'gochi-white',
}"
:auto-detect="false"
:export-options="{ quality: 0.8 }"
/>
<button type="button" @click="save">保存当前图片</button>
</template>dialog 模式下也可以通过 editorRef.value.loadImage(file) 主动打开弹窗;关闭弹窗只隐藏界面,不会清除当前编辑状态。inline 与 dialog 都提供底部保存按钮,业务需要自定义保存位置时,可以调用 ref 暴露的 save()、export() 或 download()。
Dialog 模式下的编辑器弹窗和预设弹窗会直接挂到 document.body 的独立 portal,使用高层级固定定位。业务容器不需要额外设置 z-index;组件卸载时会移除 portal。
自定义保存操作
如果保存结果需要上传到业务接口,可以通过 saveHandler Prop 接管组件内置保存按钮和 ref.save():
<script setup lang="ts">
import { PlateEditor } from '@composy/plate-editor-vue'
import type { PlateEditorSaveContext } from '@composy/plate-editor-vue'
const saveHandler = async ({ blob, filename, options, download }: PlateEditorSaveContext) => {
const body = new FormData()
body.append('file', blob, `${filename}.${options.type === 'image/jpeg' ? 'jpg' : 'png'}`)
await fetch('/api/plate-images', { method: 'POST', body })
// 不调用 download() 就不会触发浏览器下载;需要双重留存时再调用它。
void download
}
</script>
<template>
<PlateEditor :save-handler="saveHandler" :auto-detect="false" />
</template>保存回调拿到的是导出结果 Blob;发生编辑或显式指定导出参数时,它是完成透视合成后的编码结果。未指定 type 时会沿用最近导入图片的格式;需要固定格式时可在 ref.export() / ref.download() / ref.save() 的第二个参数中传入 { type: 'image/webp', quality: 0.75 },或通过 export-options 配置 Studio 默认值。若直接导入 File/Blob 且没有任何编辑或导出参数,组件会复用原始 Blob,避免保存后体积无故增大。保存成功后触发 save-complete,回调抛错时触发 save-error 并继续向调用方抛出异常;未传 saveHandler 时保持默认浏览器下载。
使用业务自定义触发按钮
在 dialog 模式下,可以使用具名 trigger 插槽替换内置启动卡片。插槽按钮会被 Core 绑定点击事件,选图后自动打开编辑对话框:
<template>
<PlateEditor
mode="dialog"
:options="{ presets: customPresets, showBuiltInPresets: false }"
:auto-detect="false"
>
<template #trigger>
<button type="button" class="choose-vehicle-button">选择车辆图片并编辑</button>
</template>
</PlateEditor>
</template>也可以把外部已有元素的 HTMLElement 或 CSS 选择器传给 trigger Prop:
<button id="choose-vehicle" type="button">选择图片</button>
<PlateEditor mode="dialog" trigger="#choose-vehicle" :auto-detect="false" />通过 ref 调用 openPicker() 可以从快捷键或业务流程主动打开系统文件选择器。自定义触发按钮只替换入口,不会影响对话框内的工具栏和保存按钮。
自定义工具栏按钮与 Lucide 图标
toolbar 是 Vue 组件提供的顶层 Prop,会原样传给 Core Studio。内置工具栏使用框架无关的 Lucide SVG;icon 可以写内置名称,也可以写 SVG body 或完整 <svg>。visible: false 隐藏按钮,label 同时影响无障碍名称和悬浮提示,attributes 可写入原生 HTML 属性。工具栏的 compression 按钮提供 100%、92%、80%、60%、40% 五档质量选择,也可隐藏:
<script setup lang="ts">
const toolbar = {
buttons: {
upload: { label: '选择车辆图片', icon: 'image-plus' },
detect: { label: '重新识别', icon: 'scan-search' },
manual: {
label: '矩形框选车牌',
icon: '<path d="M5 4h14v16H5z"/><path d="M8 8h8M8 12h5" />',
},
'manual-corners': { label: '四角点选车牌', icon: 'scan-line' },
pan: { label: '移动画布', icon: 'hand' },
compression: { label: '选择图片压缩质量', attributes: { 'data-role': 'compression' } },
compare: { visible: false },
},
} as const
</script>
<template>
<PlateEditor
:toolbar="toolbar"
:options="{ presets: customPresets, showBuiltInPresets: false }"
:auto-detect="false"
/>
</template>如果需要替换整个按钮,可以在 toolbar.buttons.<id>.element 中传入业务创建的 HTMLButtonElement;如果只需要改变行为,传入 onClick 即可。save 也是可配置按钮:inline 模式会把它放在工具栏最右侧,dialog 模式会把它放在编辑对话框右下角。默认动作可以通过 ref 的 save() 复用:
const saveButton = document.createElement('button')
saveButton.type = 'button'
saveButton.textContent = '保存到业务系统'
const toolbar = {
buttons: {
save: {
element: saveButton,
onClick: () => void editorRef.value?.save('vehicle-plate'),
},
},
}Core 的 handleSize 与 edgeHandleSize 放在 options 中配置。选中车牌后会显示四个小角点和四个边中点手柄:上/下边调整高度,左/右边调整宽度,结果会限制在原图范围内,并支持撤销/重做:
<PlateEditor
:options="{
handleSize: 6,
edgeHandleSize: 6,
presets: customPresets,
showBuiltInPresets: false,
}"
:auto-detect="false"
/>Props 完整说明
| Prop | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| src | PlateSource | 无 | 初始图片,支持 URL、File、Blob、ArrayBuffer、HTMLImageElement 和 ImageBitmap |
| options | Omit<PlateEditorOptions, 'container' \| 'image' \| 'autoDetect'> | {} | 透传 Core 编辑器配置;container、image、autoDetect 由 Vue 顶层 Props 接管 |
| readonly | boolean | undefined | 是否只读;未设置时继承 options.readonly |
| autoDetect | boolean | true | 图片加载完成后是否自动识别;变化时同步到当前 Studio。仅在 options 里配置了 ONNX 模型或自定义 detector 时才会真正自动识别,否则跳过 |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | 工具栏主题 |
| primaryColor | string | undefined | 工具栏主色;变化时同步,传 undefined 恢复默认主题色 |
| mode | 'inline' \| 'dialog' | 'inline' | 直接展示编辑器,或选择图片后打开对话框 |
| trigger | HTMLElement \| string | undefined | dialog 模式复用已有触发元素;也可使用 #trigger 插槽;创建时读取 |
| toolbar | PlateEditorToolbarOptions | {} | 自定义工具栏按钮;创建时传给 Studio;compression 可隐藏质量选择 |
| exportOptions | PlateExportOptions | {} | Studio 工具栏和无参数导出/保存的默认格式、质量、尺寸与底色 |
| saveHandler | PlateEditorSaveHandler | undefined | 接管保存按钮和 save() 的 Blob 处理;ref 的单次 handler 优先级更高 |
| height | string \| number | 'min(760px, 78vh)' | Vue 宿主容器高度;数字会转换为像素 |
如果需要同时配置多个 Core 参数,推荐使用 options:
<PlateEditor
:options="{
maxPlates: 2,
minimumDetectionConfidence: 0.5,
}"
:src="image"
/>组件会将 Vue Props 转换为 Core 配置。顶层 src、autoDetect、theme 和 primaryColor 会建立 watcher;其它大多数 Core 初始化参数见后文的响应式边界。
readonly 的优先级是:显式传入的 readonly Prop > options.readonly > Core 默认值。height 为数字时转换为 ${value}px;dialog 模式使用 trigger 插槽或 trigger Prop 时,页面流中的 host 高度会自动变为 auto。
options:Core 参数透传
options 类型是 Omit<PlateEditorOptions, 'container' | 'image' | 'autoDetect'>,可传以下 Core 参数:
| 分组 | 参数 |
| --- | --- |
| 检测器 | detector、model、useHeuristicFallback、minimumDetectionConfidence、maxPlates |
| 编辑行为 | readonly、historyLimit、handleSize、edgeHandleSize |
| 图片限制 | maxFileSize、maxImageDimension、maxPixels、crossOrigin、background |
| 样式与预设 | initialStyle、showBuiltInPresets、presets、defaultPresetId |
| 元数据 | locale |
<PlateEditor
:options="{
model: {
url: '/models/license-plate-detector.onnx',
preferredBackends: ['webgpu', 'wasm'],
outputFormat: 'corners',
coordinateFormat: 'normalized',
},
maxPlates: 2,
minimumDetectionConfidence: 0.5,
maxFileSize: 10 * 1024 * 1024,
maxImageDimension: 8000,
maxPixels: 40_000_000,
showBuiltInPresets: true,
}"
:src="image"
:auto-detect="false"
/>Core 参数的默认值、嵌套类型和模型字段详见 Core 构造参数 和 Core ONNX 参数。
toolbar:工具栏定制
按钮 ID:upload、detect、manual、manual-corners、pan、presets、zoom-out、zoom-in、fit、undo、redo、compare、compression、download、theme、save。
const toolbar = {
buttons: {
upload: { label: '选择车辆图片', icon: 'image-plus' },
detect: { label: '重新识别车牌', icon: 'scan-search' },
manual: {
label: '矩形框选车牌',
icon: '<path d="M5 4h14v16H5z"/><path d="M8 8h8M8 12h5"/>',
},
'manual-corners': { label: '四角点选车牌', icon: 'scan-line' },
pan: { label: '移动画布', icon: 'hand' },
compare: { visible: false },
},
} as const| 字段 | 类型 | 说明 |
| --- | --- | --- |
| visible | boolean | false 隐藏按钮 |
| label | string | 同时影响无障碍名称、title 和 tooltip |
| icon | string | 内置 Lucide 名称、SVG body 或完整 SVG |
| element | HTMLButtonElement | 替换默认原生按钮 |
| onClick | (event: MouseEvent) => void | 替换默认动作 |
| attributes | Record<string, string \| number \| boolean \| null \| undefined> | 写入原生 HTML 属性;false/空值会移除属性 |
<PlateEditor :toolbar="toolbar" :auto-detect="false" />全部组件事件
| 事件 | 载荷 | 触发时机 |
| --- | --- | --- |
| ready | Editor | Core 编辑器实例创建完成 |
| lifecycle-ready | { width, height } | 初始布局和历史状态完成 |
| before-destroy | 无 | Core 销毁开始前 |
| lifecycle-destroy | 无 | Core 销毁完成 |
| image-load-start | { source } | 开始加载图片 |
| image-loaded | { width, height } | 图片加载完成 |
| image-error | { error } | 图片加载失败 |
| image-clear | 无 | 清空当前图片 |
| model-ready | { backend } | 模型初始化完成 |
| model-error | { error } | 模型初始化失败 |
| detect-start | { precise } | 开始识别 |
| detect-complete | { plates, precise } | 检测完成 |
| detect-error | { error } | 检测失败 |
| interaction-start | { type, plateId? } | 开始角点、边、图层移动、平移或手动框选 |
| interaction-end | { type, plateId?, changed } | 交互结束;changed 表示是否真的改变 |
| viewport-change | { fitScale, scale, zoom, x, y } | 视口发生变化 |
| plate-add | { plate } | 图层新增 |
| plate-select | { plate: Plate \| null } | 图层选择变化 |
| plate-update | { plate } | 图层更新 |
| plate-remove | { plateId } | 图层删除 |
| preset-change | { presets, defaultPresetId } | 预设目录或默认预设变化 |
| history-change | { canUndo, canRedo } | 历史栈变化 |
| render-start / render-complete | { width, height } | 预览画布开始 / 完成绘制 |
| export-start | { options } | 开始编码导出图片 |
| export-complete | { blob, options } | 导出完成 |
| export-error | { error } | 导出失败 |
| save-start | { filename, options } | 开始执行保存 |
| save-complete | { blob, filename, options, download } | 自定义保存回调完成或默认下载完成 |
| save-error | { error, filename, options } | 保存失败 |
| select | Plate \| null | 当前车牌图层变化 |
| change | { plates, selectedPlateId } | 任意可持久化编辑状态变化 |
| export | { blob, options } | 导出完成 |
| error | Error | image-error 和 detect-error 的错误兼容别名 |
| dialog-open / dialog-close | { type: 'editor' \| 'presets' } | 编辑对话框或预设弹窗打开 / 关闭 |
| destroy | 无 | 组件销毁 |
除 ready、select、export 和 error 这几个兼容性别名外,Vue 事件名称是 Core 事件名的 kebab-case 映射。例如 Core 的 plate:update 对应 @plate-update,save:complete 对应 @save-complete。所有事件都可以直接在模板上监听,也可以通过 getEditor() / getStudio() 使用 Core 的 on/off。
Core 的冒号事件映射为 kebab-case,例如 plate:update 对应 @plate-update,save:complete 对应 @save-complete。ready、select、export、error 是 Vue 额外提供的语义别名。
<PlateEditor
@image-error="({ error }) => console.error(error)"
@detect-complete="({ plates }) => console.log(plates)"
@plate-update="({ plate }) => console.log(plate.id)"
@save-complete="context => console.log(context.blob.size)"
/>ref 暴露 API
const editorRef = ref<PlateEditorExpose>()
const editor = editorRef.value?.getEditor()
const studio = editorRef.value?.getStudio()
await editorRef.value?.loadImage(file)
await editorRef.value?.detectPlates({ precise: true })
const blob = await editorRef.value?.export({ type: 'image/png' })实例访问
| 方法 | 说明 |
| --- | --- |
| getEditor() | 获取底层 Core Editor |
| getStudio() | 获取 PlateEditorStudio |
| editor | 当前 Core 编辑器引用 |
| studio | 当前 Studio 引用 |
图片、模型和检测
| 方法 | 说明 |
| --- | --- |
| loadImage(source) | 加载图片 |
| preload() | 预加载模型和检测器 |
| setModel(model) | setModel(model: ModelOptions & { url: string }): Promise<void>;切换 ONNX 模型 |
| detectPlates(options?) | detectPlates(options?: { precise?: boolean; replace?: boolean }): Promise<Plate[]>;执行识别 |
主题和视口
| 方法 | 说明 |
| --- | --- |
| setTheme(theme) | 设置 light、dark 或 auto |
| setPrimaryColor(color?) | 设置主色,传 undefined 可恢复全局主题色 |
| setZoom(zoom) | setZoom(zoom: number): void;设置相对适配视口的缩放倍数 |
| zoomBy(factor) | zoomBy(factor: number): void;按倍数放大或缩小 |
| resetViewport() | resetViewport(): void;恢复适配视口 |
| getViewport() | getViewport(): PlateEditorViewport \| undefined;获取当前视口状态 |
图层图片、预设、状态和导出
| 方法 | 说明 |
| --- | --- |
| setAddMode(mode) | setAddMode(true \| 'drag' \| 'corners' \| false);选择拖拽矩形框选、依次点击四角,或取消添加 |
| addPlate(corners) | addPlate(corners: Plate['corners']): Plate \| undefined;使用四个角点手动新增图层 |
| setPlateImage(id, source, options?) | setPlateImage(id, source, { fit?, opacity?, contentMode? }): Promise<Plate \| null \| undefined>;设置图层图片 |
| clearPlateImage(id) | clearPlateImage(id: string): Plate \| null \| undefined;清除图层图片 |
| serialize() / restore(state) | serialize(): PlateEditorState \| undefined、restore(state): Promise<void>;保存或恢复状态 |
| getPresets() | getPresets(): PlatePreset[];获取当前预设副本 |
| registerPreset(preset) | registerPreset(input): PlatePreset;注册预设 |
| updatePreset(id, patch) | updatePreset(id, input): PlatePreset \| null;更新预设 |
| removePreset(id) | removePreset(id: string): boolean;删除预设 |
| setDefaultPreset(id) | setDefaultPreset(id: string \| null): boolean;设置或清空默认预设 |
| applyPreset(id, plateId?) | applyPreset(id: string, plateId?: string \| null): Promise<Plate \| null>;应用预设 |
| export(options?) | export(options?: PlateExportOptions): Promise<Blob>;导出 Blob |
| download(filename?, options?) | download(filename?: string, options?: PlateExportOptions): Promise<void>;导出并触发浏览器下载 |
| save(filename?, options?, handler?) | save(filename?, options?, handler?): Promise<void>;单次 handler 优先于 Prop 的 saveHandler |
| openPicker() | openPicker(): void;主动打开文件选择器,适合自定义按钮、快捷键和外部表单流程 |
PlateEditorExpose 提供 setAddMode(true | 'drag' | 'corners' | false) 便于直接切换两种添加方式。需要撤销重做、修改已有图层或访问更底层能力时,先通过 getEditor() 获取 Core Editor,再调用 Core API。
内置 Studio 工具栏会将矩形框选与四角点选呈现为一个互斥的“添加方式”按钮选择器,而不是两个同时激活的按钮。点击当前方式后,选项面板会从 toolbar 上方向上滑出,并在每个选项前显示对应图标。
通过 getStudio() 可以访问 Studio 的 on/off 事件代理、autoDetectionEnabled、setAutoDetect() 等 Core 未在 Vue expose 中重复包装的能力;通过 getEditor() 可以使用 undo()、redo()、setDetector()、detectWithLargeModel()、updatePlate()、updatePlateStyle()、removePlate()、selectPlate()、copyStyleToAll()、setAddMode() 和 setComparison() 等底层 API。
例如:
const editor = editorRef.value?.getEditor()
editor?.setAddMode(true)
editor?.undo()
editor?.updatePlateStyle(plateId, { text: '粤A·88888' })
const studio = editorRef.value?.getStudio()
studio?.setAutoDetect(false)
studio?.on('dialog:open', payload => console.log(payload.type))在模板中调用 ref
<script setup lang="ts">
import { ref } from 'vue'
import type { PlateEditorExpose } from '@composy/plate-editor-vue'
const editorRef = ref<PlateEditorExpose>()
async function save() {
await editorRef.value?.download('plate-result', {
type: 'image/jpeg',
quality: 0.92,
})
}
</script>
<template>
<PlateEditor ref="editorRef" :auto-detect="false" />
<button type="button" @click="save">保存</button>
</template>响应式更新边界
组件创建时会把 Props 合并为一次 PlateEditorStudio 配置。以下 Props 在组件已经挂载后仍会同步到当前实例:
| Prop | 运行时行为 |
| --- | --- |
| src | 新值调用 loadImage();变为 undefined 时调用 clearImage() |
| theme | 调用 studio.setTheme() |
| primaryColor | 调用 studio.setPrimaryColor();传 undefined 恢复全局颜色 |
| autoDetect | 调用 studio.setAutoDetect() |
options、readonly、mode、trigger、toolbar 和 saveHandler 主要是创建时配置。改变这些值不会自动重建 Studio,也不会替换已经创建的 DOM 按钮或保存回调;如果需要切换它们,建议使用 v-if 销毁后重新挂载,或直接通过 getStudio() / getEditor() 调用对应运行时 API。height 会参与宿主容器样式计算,但不会改变 Studio 的初始化模式。
组件尚未 mounted 或已经 unmounted 时,ref 方法可能返回 undefined、null、空数组或直接等待一个未创建实例;需要强制保证编辑器就绪时,应在 ready 或 lifecycle-ready 事件之后调用。
图片、模型、检测和预设
Vue 组件不会内置模型文件。Vite 项目可以将模型放在:
public/models/license-plate-detector.onnx然后通过 options 配置模型:
<PlateEditor
:options="{
model: {
url: '/models/license-plate-detector.onnx',
outputFormat: 'corners',
coordinateFormat: 'normalized',
preferredBackends: ['webgpu', 'wasm'],
},
}"
:src="image"
:auto-detect="false"
@detect-complete="({ plates }) => console.log(plates)"
/>预设默认完全由业务配置,showBuiltInPresets 默认是 false。需要内置样式时显式传入 showBuiltInPresets: true,也可以只传一个或多个自定义 presets;只有一个自定义预设时它会自动成为默认样式,多个预设可以通过 defaultPresetId 指定默认项。四颗安装螺帽是可选细节,默认不显示,仅在预设里显式设置 style.mountingBolts: { enabled: true } 时才绘制;文字、图片和混合内容的详细选项请查看 Core 车牌内容文档。
内置 ONNX 检测器
options.model 支持 url、后端顺序、输入输出布局、坐标格式、阈值、WASM 路径和候选校验等参数。模型必须能被浏览器通过 fetch 读取;跨域模型服务器需要正确的 CORS 响应。完整字段和默认值见 Core ONNX 参数。
const options = {
model: {
url: '/models/license-plate-detector.onnx',
preferredBackends: ['webgpu', 'wasm'],
scoreThreshold: 0.35,
nmsThreshold: 0.45,
inputWidth: 640,
inputHeight: 640,
inputLayout: 'nchw',
channelOrder: 'rgb',
normalization: 'zero-one',
outputFormat: 'corners',
coordinateFormat: 'normalized',
verifyCandidates: true,
heuristicFallback: true,
},
}远程大模型检测
远程大模型不是 options.model 的替代品,而是通过 Core Editor 的显式方法按需执行。Vue ref 没有复制这个低频底层方法,可在 ready 后通过 getEditor() 调用:
const editor = editorRef.value?.getEditor()
const plates = await editor?.detectWithLargeModel({
endpoint: 'https://api.example.com/v1/chat/completions',
model: 'vision-model',
apiKey: runtimeApiKey,
coordinateFormat: 'auto',
timeout: 30_000,
imageQuality: 0.9,
}, { replace: true })endpoint、model 是必填项;apiKey、自定义 headers、prompt、coordinateFormat、maxImageSide、imageMimeType、imageQuality、timeout、maxTokens、temperature 和 extraBody 都属于运行时请求参数。不要把 API key 写入源代码、Props 默认值或 serialize() 的持久化状态。远程服务应返回能被 Core 解析为车牌四角点的结构,并自行处理鉴权、限流和隐私合规。
自定义检测器
需要接入业务服务或其它推理引擎时,可通过 options.detector 传入 PlateEditorDetector。检测器接收当前图片的 Canvas 上下文,返回图片坐标系中的四角点:
import type { PlateEditorDetector } from '@composy/plate-editor-vue'
const detector: PlateEditorDetector = {
backend: 'custom',
async preload() {
// 加载业务模型或建立连接
},
async detect({ image, width, height }, { precise, maxResults }) {
const result = await requestPlateDetection({ image, width, height, precise })
return result.slice(0, maxResults ?? 4).map(item => ({
corners: item.corners,
confidence: item.confidence,
type: 'unknown',
source: 'detected',
}))
},
destroy() {
// 释放业务检测器资源
},
}corners 顺序为左上、右上、右下、左下;confidence、cornerConfidences、type 和 source 可选。若检测器实现了 preload,ref.preload() 会等待它完成;若实现了 destroy,组件卸载时 Core 会调用它。
类型和数据结构
Vue 包重新导出了常用 Core 类型,推荐直接从 Vue 包导入组件相关类型:
import type {
ModelOptions,
Plate,
PlateEditorExpose,
PlateEditorOptions,
PlateEditorSaveContext,
PlateEditorState,
PlateEditorViewport,
PlateExportOptions,
PlatePreset,
PlateSource,
PlateStyle,
} from '@composy/plate-editor-vue'PlateSource
src、loadImage() 和 setPlateImage() 支持 string | Blob | File | ArrayBuffer | HTMLImageElement | ImageBitmap。字符串可以是图片 URL、Data URL 或浏览器可访问的路径。URL 图片受 CORS 规则约束;导出、检测和 Canvas 读取时,远程图片服务器必须允许当前来源。
Plate 与 PlateStyle
Plate 至少包含 id、corners、type、source、confidence、cornerConfidences、visible、locked 和 style。corners 是四个 { x, y } 点,坐标以原图像素为单位。style 支持 contentMode: 'text' | 'image' | 'both'、背景渐变、文字排版、image: { source, fit, opacity }、边框、阴影、圆角、内边距和 mountingBolts。
PlateEditorState 与 PlateEditorViewport
serialize() 返回包含 version、width、height、plates 和 selectedPlateId 的状态对象,可能带有 imageSource。它适合保存编辑状态,但不应当用来保存 API key 或其它凭证。getViewport() 返回 fitScale、scale、zoom、x、y;viewport-change 事件也会发送同样的结构。
导出与保存类型
PlateExportOptions 支持 type(PNG/JPEG/WebP)、quality、useOriginalResolution、scale、maxWidth、maxHeight 和 backgroundColor。JPEG/WebP 重新编码时默认质量为 0.8;saveHandler 接收 PlateEditorSaveContext:blob 是导出结果,filename 是建议文件名,options 是本次导出参数,download() 可继续调用默认浏览器下载。
样式产物
推荐:导入包根 CSS
import '@composy/plate-editor-vue/index.css'生产环境导入压缩 CSS
import '@composy/plate-editor-vue/index.min.css'使用 preserve-modules 样式入口
import '@composy/plate-editor-vue/style/index.css'对于 ESM,style/index.css 解析到 es/style/index.css;对于 CommonJS,解析到 lib/style/index.css。Core 的 Canvas 和工具栏运行时样式由 Core 自动注入,Vue CSS 主要负责 Vue 宿主容器的基础布局。
UMD 浏览器用法
UMD 页面需要按顺序加载 Vue、Core UMD 和 Vue 适配包 UMD:
<script src="./vendor/vue.global.prod.js"></script>
<script src="./core/dist/index.min.js"></script>
<script src="./vue/dist/index.min.js"></script>
<div id="app"></div>
<script>
const app = Vue.createApp({
components: {
PlateEditor: LDesignPlateEditorVue.PlateEditor,
},
template: `<PlateEditor :options="{ autoDetect: false }" />`,
})
app.mount('#app')
</script>全局对象名称:
- Core:
LDesignPlateEditor; - Vue 适配包:
LDesignPlateEditorVue。
UMD 不会自动加载 Vue、模型权重、onnxruntime-web 或 WASM 文件。完整静态页面请参考 Vue UMD 示例。
CommonJS 用法
const { createApp } = require('vue')
const {
PlateEditor,
BUILT_IN_PLATE_PRESETS,
} = require('@composy/plate-editor-vue')
const app = createApp(App)
app.component('PlateEditor', PlateEditor)
app.provide('platePresets', BUILT_IN_PLATE_PRESETS)
app.mount('#app')CommonJS 入口适合 CommonJS bundler。浏览器页面中仍然需要由 bundler 提供 Vue、DOM 和 CSS。
示例和 Playground
- Vue 示例索引:SFC、ESM、CommonJS、UMD 和 CSS 产物;
- 完整 Vue SFC:模型、预设、事件、ref 和导出;
- CommonJS 示例:注册
PlateEditor; - UMD 示例:Vue 全局对象、Core UMD 和 Vue UMD 的完整加载顺序;
- 完整 Playground:首页、Core/Vanilla 演示、Vue 演示、使用文档和 API 文档。
构建和销毁
pnpm --dir libraries/plate-editor/packages/core run build
pnpm --dir libraries/plate-editor/packages/vue run type-check
pnpm --dir libraries/plate-editor/packages/vue run build组件卸载时会自动销毁 Core 实例。若业务绕过 Vue 组件、手动创建了 PlateEditorStudio,则需要自行调用 studio.destroy()。这可以释放事件监听、ResizeObserver、模型资源、对象 URL 和编辑器 DOM。
