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

@yuwenovo/ocr-result-view

v2.2.2

Published

Vue 3 OCR document comparison result viewer with virtualized Canvas rendering

Readme

@yuwenovo/ocr-result-view

Vue 3 OCR 文档比对结果查看器。传入两份文档的页面图片和差异数据,即可展示双栏预览、差异标注、连线、差异列表和分页导航。

组件负责展示与交互;OCR 识别、文档比对、接口请求和审阅保存由接入项目负责。默认只读,无需 Vue Router、Pinia、Axios 或 Tailwind。

预览区提供默认开启的“同步滚动”开关。开启时以最近滚动的一侧为基准(初始为标准文档),按可滚动距离的百分比同步另一侧;滚动任意一侧或使用分页按钮均可联动。两份文档页数不同时仍按整体进度同步,不表示内容逐段对应。点击差异仍分别定位两侧的实际标注,关闭开关后恢复独立滚动。

1. 安装

在 Vue 3.5+ 项目中安装:

pnpm add @yuwenovo/ocr-result-view

2. 最小接入示例

将两张文档页面图片放到接入项目的 public/previews/source-1.png 和 public/previews/target-1.png,然后创建以下 Vue 页面。示例中的尺寸、文本和标注坐标需替换为实际结果。

<script setup lang="ts">
import { OcrResultView, type DiffDetail, type PageMeta } from '@yuwenovo/ocr-result-view'
import '@yuwenovo/ocr-result-view/style.css'

const sourcePages: PageMeta[] = [
  { imageUrl: '/previews/source-1.png', width: 1200, height: 1600 },
]
const targetPages: PageMeta[] = [
  { imageUrl: '/previews/target-1.png', width: 1200, height: 1600 },
]
const diffDetail: DiffDetail = [
  {
    diffId: 'diff-1',
    type: 'modify',
    ignored: false,
    delChars: '原文',
    insChars: '新文',
    sourceText: '原文',
    targetText: '新文',
    sourceLine: 0,
    targetLine: 0,
    sourceBox: [100, 100, 180, 130],
    targetBox: [100, 100, 180, 130],
    srcPage: 0,
    tgtPage: 0,
  },
]
</script>

<template>
  <div style="height: 720px">
    <OcrResultView
      :diff-detail="diffDetail"
      :source-pages="sourcePages"
      :target-pages="targetPages"
      source-filename="原文件.pdf"
      target-filename="比对文件.pdf"
    />
  </div>
</template>

接入时必须导入 style.css,并给容器设置明确高度。组件高度为 100%。

带自定义导航按钮的示例见 examples/Basic.vue。它接收一个 result 属性,内容为本组件的 Props。

3. 准备数据

三个必填属性是 sourcePages、targetPages 和 diffDetail。无差异时传 diffDetail: [],仍然可以预览文档。

页面图片

每页使用一个 PageMeta 对象,数组顺序即页码顺序:

interface PageMeta {
  imageUrl: string
  width?: number
  height?: number
}

imageUrl 必须是浏览器可以访问的图片地址,不是 PDF 地址。width 和 height 是原图像素尺寸,建议提供,避免图片加载前后布局变化。

差异列表

DiffDetail 是已排好展示顺序的扁平数组。每一项必须包含以下公共字段:

| 字段 | 含义 | | --- | --- | | diffId | 本次结果内唯一且稳定的字符串 ID | | type | modify(修改)、delete(删除)或 insert(新增) | | ignored | 是否已忽略;未审阅时为 false | | sourceText / targetText | 两侧对应的上下文文本;无文本时使用空字符串 | | sourceLine / targetLine | 两侧行索引,从 0 开始 | | srcPage / tgtPage | 两侧页面数组下标,从 0 开始 |

不同类型还需要以下字段:

| 类型 | 必填字段 | 可选字段 | | --- | --- | --- | | modify | delChars、insChars、sourceBox、targetBox | sourceChangeRanges、targetChangeRanges | | delete | chars、sourceBox、tgtGap | — | | insert | chars、targetBox、srcGap | — |

  • delChars / insChars 是修改前后的文本,chars 是删除或新增的文本。
  • sourceBox / targetBox 使用原图像素坐标 [x1, y1, x2, y2],无对应框时传 null。不要传页面缩放后的坐标。
  • srcGap / tgtGap 表示另一侧对应的空缺位置,格式为 { x, y, w, h },无位置时传 null。
  • 修改项可以提供字符高亮范围 { start, end }[],使用 Unicode 字符索引,包含 start、不包含 end。
  • 表格差异可额外传 domain: 'table' 和 tableChange,可从包入口导入 TableChange 类型查看可选值。

公共类型均可从包入口导入:

import type { DiffDetail, DiffItem, PageMeta, ComparisonReview } from '@yuwenovo/ocr-result-view'

如果后端返回的是 snake_case 字段或包含 code/message/data 的响应,应在接入项目中转换为上述结构,不能将整个响应直接传给 diffDetail。

4. 接入忽略 / 撤销审阅

只预览时可以跳过本节。需要审阅时,设置 :readonly="false" 并监听 ignore。

审阅流程为:用户点击 → 组件发出事件 → 接入项目保存到服务端 → 回传最新差异和统计。组件不会自行修改 ignored,也不会发送保存请求。

下面是审阅容器示例。父页面提供初始数据和 saveReview 函数,该函数封装你自己的保存接口,并返回服务端最新结果。

<script setup lang="ts">
import { ref } from 'vue'
import {
  OcrResultView,
  type ComparisonReview,
  type OcrResultViewProps,
} from '@yuwenovo/ocr-result-view'
import '@yuwenovo/ocr-result-view/style.css'

type ReviewResult = OcrResultViewProps & { review: ComparisonReview }
const props = defineProps<{
  initialResult: ReviewResult
  saveReview: (request: {
    diffId: string | null
    ignored: boolean
    reviewVersion: number
  }) => Promise<ReviewResult>
}>()

const result = ref(props.initialResult)
const busy = ref(false)
const error = ref('')

async function handleIgnore(diffId: string | null, ignored: boolean) {
  if (busy.value) return
  busy.value = true
  error.value = ''
  try {
    result.value = await props.saveReview({
      diffId,
      ignored,
      reviewVersion: result.value.review.review_version,
    })
  } catch {
    error.value = '保存失败,请重试'
  } finally {
    busy.value = false
  }
}
</script>

<template>
  <p v-if="error" role="alert">{{ error }}</p>
  <div style="height: 720px">
    <OcrResultView
      v-bind="result"
      :readonly="false"
      :review-busy="busy"
      @ignore="handleIgnore"
    />
  </div>
</template>

diffId === null 表示操作整个任务的全部差异,包含当前筛选条件外的项;ignored === true 表示忽略,false 表示撤销忽略。批量确认、版本冲突和重试策略由接入项目处理。

review 使用服务端返回的汇总:

interface ComparisonReview {
  review_version: number
  total_difference_count: number
  ignored_difference_count: number
  remaining_difference_count: number
  comparison_passed: boolean
}

不传 review 时,组件不会自行推断比对是否通过。切换任务时给组件或上述审阅容器设置 :key="taskId";同一任务保存成功后替换差异数组和审阅汇总。

5. API 参考

Props

| 属性 | 类型 | 默认值 / 说明 | | --- | --- | --- | | diffDetail | DiffDetail | 必填,差异数组 | | sourcePages / targetPages | PageMeta[] | 必填,两侧页面图片 | | sourceFilename / targetFilename | string | 默认显示“标准文档” / “对比文档” | | readonly | boolean | true;设为 false 显示审阅按钮 | | review | ComparisonReview | 可选,审阅统计及通过结论 | | reviewBusy | boolean | false;保存期间禁用审阅按钮 | | imageCrossOrigin | 'anonymous' \| 'use-credentials' \| null | 'anonymous';null 表示不设置图片 crossOrigin |

事件

| 事件 | 回调参数 | 说明 | | --- | --- | --- | | ignore | (diffId: string \| null, ignored: boolean) | 请求忽略或撤销,详见审阅接入 | | select | (item: DiffItem \| null, index: number) | 选中项变化;index 为原始差异数组下标,无选中项时为 -1 | | page-change | (side: 'source' \| 'target', index: number) | 当前页变化;index 从 0 开始 | | filter-change | (tab: DiffTab, review: ReviewFilter) | 差异类型或审阅筛选变化 | | image-error | (url: string) | 图片加载失败 |

实例方法

通过组件 ref 调用,例如:

<script setup lang="ts">
import { ref } from 'vue'
import { OcrResultView, type OcrResultViewExpose } from '@yuwenovo/ocr-result-view'

const viewer = ref<OcrResultViewExpose>()
// 组件挂载后调用:
// await viewer.value?.selectDiff('diff-1')
// viewer.value?.setFilter('all', 'all')
</script>

<!-- 将 ref="viewer" 加到已传入页面和差异数据的 OcrResultView 上。 -->

| 方法 | 说明 | | --- | --- | | selectDiff(indexOrId): Promise<boolean> | 按原数组下标或 ID 定位;被筛选隐藏时切换为全部,无效参数返回 false | | nextDiff(): boolean / previousDiff(): boolean | 按当前筛选结果导航,到边界返回 false | | goToPage(side, index): boolean | 跳至指定侧页面,index 从 0 开始;越界返回 false | | setFilter(tab, review?) | 设置筛选;省略 review 时保留当前审阅筛选 | | resize() | 容器布局变化后请求重新布局;内部也监听 ResizeObserver | | retryImages() | 清除图片失败记录,重新加载可见及缓冲页面 |

DiffTab 为 all / modify / delete / insert;ReviewFilter 为 pending / ignored / all。初始筛选为 all + all,显示全部差异,包含已忽略项。

插槽

| 插槽 | 参数 | 用途 | | --- | --- | --- | | toolbar | activeIndex, selectDiff, setFilter | 顶部工具栏 | | source-title / target-title | filename | 自定义文档标题,保留内置页码导航 | | diff-card | item, index, active, select, ignore | 替换差异卡片内容,保留外层容器 | | empty | 无 | 没有页面数据时的内容 | | footer | activeIndex, review | 底部业务信息 |

例如,将以下插槽放在 OcrResultView 内:

<template #diff-card="{ item, index, active, select }">
  <button :aria-pressed="active" @click="select()">
    {{ index + 1 }} · {{ item.type }} · {{ item.diffId }}
  </button>
</template>

卡片插槽的 ignore(boolean) 会触发审阅事件,只读或忙碌时不触发。自定义业务按钮的权限由接入项目控制。

6. 常见问题

组件空白或高度不对?

确认已导入 @yuwenovo/ocr-result-view/style.css,容器有明确高度,页面数组非空。弹窗或折叠面板展开后可调用 resize()。

图片加载失败?

先在浏览器网络面板检查图片地址、状态码和跨域响应头。默认使用 anonymous;跨域 Cookie 图片需要设置 image-cross-origin="use-credentials",并让图片服务允许当前来源及凭证。修复访问问题后调用 retryImages()。

图片请求不能自定义 Authorization 请求头。需要这类鉴权时,由接入项目提供同源代理地址、短期签名 URL,或先请求图片再传入 Blob URL;Blob URL 由接入项目负责释放。

为什么差异没有显示,或忽略后消失了?

默认显示全部差异,包含已忽略项。使用筛选栏查看“未忽略”或“已忽略”,或调用 setFilter('all', 'pending') 只查看未忽略差异。标注错位时检查原图尺寸、坐标和从 0 开始的页码。

如何用于 Nuxt / SSR?

查看器使用浏览器 Canvas 和图片 API,在客户端挂载,例如放在 Nuxt 的 ClientOnly 中,并为外层容器设置高度。

如何修改主题?

默认深色。可在组件或祖先元素上覆盖 CSS 变量,例如 --viewer-bg、--viewer-panel、--viewer-border、--viewer-card-text。例如:

<template>
  <div class="viewer-container">
    <!-- 在此放入已配置数据的 OcrResultView -->
  </div>
</template>

<style scoped>
.viewer-container {
  height: 720px;
  --viewer-bg: #111827;
  --viewer-panel: #1f2937;
  --viewer-border: #374151;
  --viewer-card-text: #e5e7eb;
}
</style>

自定义图片加载

imageLoader?: (url: string) => Promise<HTMLImageElement> 可由宿主提供带鉴权的图片加载器,返回已完成加载/解码的图片。组件仍控制可见页调度、四路并发和图片缓存;不提供时沿用 imageCrossOrigin 和原始图片地址。请求头、会话、取消和临时 Blob URL 的释放由宿主管理,组件不持有业务凭据。