npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@composy/plate-editor-vue

v0.0.5

Published

Vue 3 adapter for @composy/plate-editor-core

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 截断。

目录

安装与环境

pnpm add vue @composy/plate-editor-vue

vue 是 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

构建和销毁

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。