@yuwenovo/ocr-result-view 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -0
- package/dist/ocr-result-view.css +2 -0
- package/dist/ocr-result-view.d.ts +1 -0
- package/dist/ocr-result-view.js +1068 -0
- package/dist/style.d.ts +2 -0
- package/dist/types/components/DiffCards.vue.d.ts +36 -0
- package/dist/types/components/OcrDiffViewer.vue.d.ts +71 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/types.d.ts +100 -0
- package/dist/types/utils/diff-items.d.ts +18 -0
- package/examples/Basic.vue +26 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# @yuwenovo/ocr-result-view
|
|
2
|
+
|
|
3
|
+
Vue 3 OCR 文档比对结果查看器。传入两份文档的页面图片和差异数据,即可展示双栏预览、差异标注、连线、差异列表和分页导航。
|
|
4
|
+
|
|
5
|
+
组件负责展示与交互;OCR 识别、文档比对、接口请求和审阅保存由接入项目负责。默认只读,无需 Vue Router、Pinia、Axios 或 Tailwind。
|
|
6
|
+
|
|
7
|
+
## 1. 安装
|
|
8
|
+
|
|
9
|
+
在 Vue 3.5+ 项目中安装:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @yuwenovo/ocr-result-view
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 2. 最小接入示例
|
|
16
|
+
|
|
17
|
+
将两张文档页面图片放到接入项目的 `public/previews/source-1.png` 和 `public/previews/target-1.png`,然后创建以下 Vue 页面。示例中的尺寸、文本和标注坐标需替换为实际结果。
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { OcrResultView, type DiffDetail, type PageMeta } from '@yuwenovo/ocr-result-view'
|
|
22
|
+
import '@yuwenovo/ocr-result-view/style.css'
|
|
23
|
+
|
|
24
|
+
const sourcePages: PageMeta[] = [
|
|
25
|
+
{ imageUrl: '/previews/source-1.png', width: 1200, height: 1600 },
|
|
26
|
+
]
|
|
27
|
+
const targetPages: PageMeta[] = [
|
|
28
|
+
{ imageUrl: '/previews/target-1.png', width: 1200, height: 1600 },
|
|
29
|
+
]
|
|
30
|
+
const diffDetail: DiffDetail = [
|
|
31
|
+
{
|
|
32
|
+
diffId: 'diff-1',
|
|
33
|
+
type: 'modify',
|
|
34
|
+
ignored: false,
|
|
35
|
+
delChars: '原文',
|
|
36
|
+
insChars: '新文',
|
|
37
|
+
sourceText: '原文',
|
|
38
|
+
targetText: '新文',
|
|
39
|
+
sourceLine: 0,
|
|
40
|
+
targetLine: 0,
|
|
41
|
+
sourceBox: [100, 100, 180, 130],
|
|
42
|
+
targetBox: [100, 100, 180, 130],
|
|
43
|
+
srcPage: 0,
|
|
44
|
+
tgtPage: 0,
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<div style="height: 720px">
|
|
51
|
+
<OcrResultView
|
|
52
|
+
:diff-detail="diffDetail"
|
|
53
|
+
:source-pages="sourcePages"
|
|
54
|
+
:target-pages="targetPages"
|
|
55
|
+
source-filename="原文件.pdf"
|
|
56
|
+
target-filename="比对文件.pdf"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
接入时必须导入 `style.css`,并给容器设置明确高度。组件高度为 `100%`。
|
|
63
|
+
|
|
64
|
+
带自定义导航按钮的示例见 [examples/Basic.vue](examples/Basic.vue)。它接收一个 `result` 属性,内容为本组件的 Props。
|
|
65
|
+
|
|
66
|
+
## 3. 准备数据
|
|
67
|
+
|
|
68
|
+
三个必填属性是 `sourcePages`、`targetPages` 和 `diffDetail`。无差异时传 `diffDetail: []`,仍然可以预览文档。
|
|
69
|
+
|
|
70
|
+
### 页面图片
|
|
71
|
+
|
|
72
|
+
每页使用一个 `PageMeta` 对象,数组顺序即页码顺序:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface PageMeta {
|
|
76
|
+
imageUrl: string
|
|
77
|
+
width?: number
|
|
78
|
+
height?: number
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`imageUrl` 必须是浏览器可以访问的图片地址,不是 PDF 地址。`width` 和 `height` 是原图像素尺寸,建议提供,避免图片加载前后布局变化。
|
|
83
|
+
|
|
84
|
+
### 差异列表
|
|
85
|
+
|
|
86
|
+
`DiffDetail` 是已排好展示顺序的扁平数组。每一项必须包含以下公共字段:
|
|
87
|
+
|
|
88
|
+
| 字段 | 含义 |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `diffId` | 本次结果内唯一且稳定的字符串 ID |
|
|
91
|
+
| `type` | `modify`(修改)、`delete`(删除)或 `insert`(新增) |
|
|
92
|
+
| `ignored` | 是否已忽略;未审阅时为 `false` |
|
|
93
|
+
| `sourceText` / `targetText` | 两侧对应的上下文文本;无文本时使用空字符串 |
|
|
94
|
+
| `sourceLine` / `targetLine` | 两侧行索引,从 `0` 开始 |
|
|
95
|
+
| `srcPage` / `tgtPage` | 两侧页面数组下标,从 `0` 开始 |
|
|
96
|
+
|
|
97
|
+
不同类型还需要以下字段:
|
|
98
|
+
|
|
99
|
+
| 类型 | 必填字段 | 可选字段 |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `modify` | `delChars`、`insChars`、`sourceBox`、`targetBox` | `sourceChangeRanges`、`targetChangeRanges` |
|
|
102
|
+
| `delete` | `chars`、`sourceBox`、`tgtGap` | — |
|
|
103
|
+
| `insert` | `chars`、`targetBox`、`srcGap` | — |
|
|
104
|
+
|
|
105
|
+
- `delChars` / `insChars` 是修改前后的文本,`chars` 是删除或新增的文本。
|
|
106
|
+
- `sourceBox` / `targetBox` 使用原图像素坐标 `[x1, y1, x2, y2]`,无对应框时传 `null`。不要传页面缩放后的坐标。
|
|
107
|
+
- `srcGap` / `tgtGap` 表示另一侧对应的空缺位置,格式为 `{ x, y, w, h }`,无位置时传 `null`。
|
|
108
|
+
- 修改项可以提供字符高亮范围 `{ start, end }[]`,使用 Unicode 字符索引,包含 `start`、不包含 `end`。
|
|
109
|
+
- 表格差异可额外传 `domain: 'table'` 和 `tableChange`,可从包入口导入 `TableChange` 类型查看可选值。
|
|
110
|
+
|
|
111
|
+
公共类型均可从包入口导入:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import type { DiffDetail, DiffItem, PageMeta, ComparisonReview } from '@yuwenovo/ocr-result-view'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
如果后端返回的是 `snake_case` 字段或包含 `code/message/data` 的响应,应在接入项目中转换为上述结构,不能将整个响应直接传给 `diffDetail`。
|
|
118
|
+
|
|
119
|
+
## 4. 接入忽略 / 撤销审阅
|
|
120
|
+
|
|
121
|
+
只预览时可以跳过本节。需要审阅时,设置 `:readonly="false"` 并监听 `ignore`。
|
|
122
|
+
|
|
123
|
+
审阅流程为:用户点击 → 组件发出事件 → 接入项目保存到服务端 → 回传最新差异和统计。组件不会自行修改 `ignored`,也不会发送保存请求。
|
|
124
|
+
|
|
125
|
+
下面是审阅容器示例。父页面提供初始数据和 `saveReview` 函数,该函数封装你自己的保存接口,并返回服务端最新结果。
|
|
126
|
+
|
|
127
|
+
```vue
|
|
128
|
+
<script setup lang="ts">
|
|
129
|
+
import { ref } from 'vue'
|
|
130
|
+
import {
|
|
131
|
+
OcrResultView,
|
|
132
|
+
type ComparisonReview,
|
|
133
|
+
type OcrResultViewProps,
|
|
134
|
+
} from '@yuwenovo/ocr-result-view'
|
|
135
|
+
import '@yuwenovo/ocr-result-view/style.css'
|
|
136
|
+
|
|
137
|
+
type ReviewResult = OcrResultViewProps & { review: ComparisonReview }
|
|
138
|
+
const props = defineProps<{
|
|
139
|
+
initialResult: ReviewResult
|
|
140
|
+
saveReview: (request: {
|
|
141
|
+
diffId: string | null
|
|
142
|
+
ignored: boolean
|
|
143
|
+
reviewVersion: number
|
|
144
|
+
}) => Promise<ReviewResult>
|
|
145
|
+
}>()
|
|
146
|
+
|
|
147
|
+
const result = ref(props.initialResult)
|
|
148
|
+
const busy = ref(false)
|
|
149
|
+
const error = ref('')
|
|
150
|
+
|
|
151
|
+
async function handleIgnore(diffId: string | null, ignored: boolean) {
|
|
152
|
+
if (busy.value) return
|
|
153
|
+
busy.value = true
|
|
154
|
+
error.value = ''
|
|
155
|
+
try {
|
|
156
|
+
result.value = await props.saveReview({
|
|
157
|
+
diffId,
|
|
158
|
+
ignored,
|
|
159
|
+
reviewVersion: result.value.review.review_version,
|
|
160
|
+
})
|
|
161
|
+
} catch {
|
|
162
|
+
error.value = '保存失败,请重试'
|
|
163
|
+
} finally {
|
|
164
|
+
busy.value = false
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<p v-if="error" role="alert">{{ error }}</p>
|
|
171
|
+
<div style="height: 720px">
|
|
172
|
+
<OcrResultView
|
|
173
|
+
v-bind="result"
|
|
174
|
+
:readonly="false"
|
|
175
|
+
:review-busy="busy"
|
|
176
|
+
@ignore="handleIgnore"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
</template>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`diffId === null` 表示操作整个任务的全部差异,包含当前筛选条件外的项;`ignored === true` 表示忽略,`false` 表示撤销忽略。批量确认、版本冲突和重试策略由接入项目处理。
|
|
183
|
+
|
|
184
|
+
`review` 使用服务端返回的汇总:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
interface ComparisonReview {
|
|
188
|
+
review_version: number
|
|
189
|
+
total_difference_count: number
|
|
190
|
+
ignored_difference_count: number
|
|
191
|
+
remaining_difference_count: number
|
|
192
|
+
comparison_passed: boolean
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
不传 `review` 时,组件不会自行推断比对是否通过。切换任务时给组件或上述审阅容器设置 `:key="taskId"`;同一任务保存成功后替换差异数组和审阅汇总。
|
|
197
|
+
|
|
198
|
+
## 5. API 参考
|
|
199
|
+
|
|
200
|
+
### Props
|
|
201
|
+
|
|
202
|
+
| 属性 | 类型 | 默认值 / 说明 |
|
|
203
|
+
| --- | --- | --- |
|
|
204
|
+
| `diffDetail` | `DiffDetail` | 必填,差异数组 |
|
|
205
|
+
| `sourcePages` / `targetPages` | `PageMeta[]` | 必填,两侧页面图片 |
|
|
206
|
+
| `sourceFilename` / `targetFilename` | `string` | 默认显示“标准文档” / “对比文档” |
|
|
207
|
+
| `readonly` | `boolean` | `true`;设为 `false` 显示审阅按钮 |
|
|
208
|
+
| `review` | `ComparisonReview` | 可选,审阅统计及通过结论 |
|
|
209
|
+
| `reviewBusy` | `boolean` | `false`;保存期间禁用审阅按钮 |
|
|
210
|
+
| `imageCrossOrigin` | `'anonymous' \| 'use-credentials' \| null` | `'anonymous'`;`null` 表示不设置图片 crossOrigin |
|
|
211
|
+
|
|
212
|
+
### 事件
|
|
213
|
+
|
|
214
|
+
| 事件 | 回调参数 | 说明 |
|
|
215
|
+
| --- | --- | --- |
|
|
216
|
+
| `ignore` | `(diffId: string \| null, ignored: boolean)` | 请求忽略或撤销,详见审阅接入 |
|
|
217
|
+
| `select` | `(item: DiffItem \| null, index: number)` | 选中项变化;index 为原始差异数组下标,无选中项时为 `-1` |
|
|
218
|
+
| `page-change` | `(side: 'source' \| 'target', index: number)` | 当前页变化;index 从 `0` 开始 |
|
|
219
|
+
| `filter-change` | `(tab: DiffTab, review: ReviewFilter)` | 差异类型或审阅筛选变化 |
|
|
220
|
+
| `image-error` | `(url: string)` | 图片加载失败 |
|
|
221
|
+
|
|
222
|
+
### 实例方法
|
|
223
|
+
|
|
224
|
+
通过组件 `ref` 调用,例如:
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<script setup lang="ts">
|
|
228
|
+
import { ref } from 'vue'
|
|
229
|
+
import { OcrResultView, type OcrResultViewExpose } from '@yuwenovo/ocr-result-view'
|
|
230
|
+
|
|
231
|
+
const viewer = ref<OcrResultViewExpose>()
|
|
232
|
+
// 组件挂载后调用:
|
|
233
|
+
// await viewer.value?.selectDiff('diff-1')
|
|
234
|
+
// viewer.value?.setFilter('all', 'all')
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<!-- 将 ref="viewer" 加到已传入页面和差异数据的 OcrResultView 上。 -->
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
| 方法 | 说明 |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `selectDiff(indexOrId): Promise<boolean>` | 按原数组下标或 ID 定位;被筛选隐藏时切换为全部,无效参数返回 `false` |
|
|
243
|
+
| `nextDiff(): boolean` / `previousDiff(): boolean` | 按当前筛选结果导航,到边界返回 `false` |
|
|
244
|
+
| `goToPage(side, index): boolean` | 跳至指定侧页面,index 从 `0` 开始;越界返回 `false` |
|
|
245
|
+
| `setFilter(tab, review?)` | 设置筛选;省略 review 时保留当前审阅筛选 |
|
|
246
|
+
| `resize()` | 容器布局变化后请求重新布局;内部也监听 ResizeObserver |
|
|
247
|
+
| `retryImages()` | 清除图片失败记录,重新加载可见及缓冲页面 |
|
|
248
|
+
|
|
249
|
+
`DiffTab` 为 `all / modify / delete / insert`;`ReviewFilter` 为 `pending / ignored / all`。初始筛选为 `all + pending`,只显示未忽略差异。
|
|
250
|
+
|
|
251
|
+
### 插槽
|
|
252
|
+
|
|
253
|
+
| 插槽 | 参数 | 用途 |
|
|
254
|
+
| --- | --- | --- |
|
|
255
|
+
| `toolbar` | `activeIndex, selectDiff, setFilter` | 顶部工具栏 |
|
|
256
|
+
| `source-title` / `target-title` | `filename` | 自定义文档标题,保留内置页码导航 |
|
|
257
|
+
| `diff-card` | `item, index, active, select, ignore` | 替换差异卡片内容,保留外层容器 |
|
|
258
|
+
| `empty` | 无 | 没有页面数据时的内容 |
|
|
259
|
+
| `footer` | `activeIndex, review` | 底部业务信息 |
|
|
260
|
+
|
|
261
|
+
例如,将以下插槽放在 `OcrResultView` 内:
|
|
262
|
+
|
|
263
|
+
```vue
|
|
264
|
+
<template #diff-card="{ item, index, active, select }">
|
|
265
|
+
<button :aria-pressed="active" @click="select()">
|
|
266
|
+
{{ index + 1 }} · {{ item.type }} · {{ item.diffId }}
|
|
267
|
+
</button>
|
|
268
|
+
</template>
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
卡片插槽的 `ignore(boolean)` 会触发审阅事件,只读或忙碌时不触发。自定义业务按钮的权限由接入项目控制。
|
|
272
|
+
|
|
273
|
+
## 6. 常见问题
|
|
274
|
+
|
|
275
|
+
**组件空白或高度不对?**
|
|
276
|
+
|
|
277
|
+
确认已导入 `@yuwenovo/ocr-result-view/style.css`,容器有明确高度,页面数组非空。弹窗或折叠面板展开后可调用 `resize()`。
|
|
278
|
+
|
|
279
|
+
**图片加载失败?**
|
|
280
|
+
|
|
281
|
+
先在浏览器网络面板检查图片地址、状态码和跨域响应头。默认使用 `anonymous`;跨域 Cookie 图片需要设置 `image-cross-origin="use-credentials"`,并让图片服务允许当前来源及凭证。修复访问问题后调用 `retryImages()`。
|
|
282
|
+
|
|
283
|
+
图片请求不能自定义 `Authorization` 请求头。需要这类鉴权时,由接入项目提供同源代理地址、短期签名 URL,或先请求图片再传入 Blob URL;Blob URL 由接入项目负责释放。
|
|
284
|
+
|
|
285
|
+
**为什么差异没有显示,或忽略后消失了?**
|
|
286
|
+
|
|
287
|
+
默认只显示未忽略差异。使用筛选栏查看“已忽略”,或调用 `setFilter('all', 'all')` 查看全部。标注错位时检查原图尺寸、坐标和从 `0` 开始的页码。
|
|
288
|
+
|
|
289
|
+
**如何用于 Nuxt / SSR?**
|
|
290
|
+
|
|
291
|
+
查看器使用浏览器 Canvas 和图片 API,在客户端挂载,例如放在 Nuxt 的 `ClientOnly` 中,并为外层容器设置高度。
|
|
292
|
+
|
|
293
|
+
**如何修改主题?**
|
|
294
|
+
|
|
295
|
+
默认深色。可在组件或祖先元素上覆盖 CSS 变量,例如 `--viewer-bg`、`--viewer-panel`、`--viewer-border`、`--viewer-card-text`。例如:
|
|
296
|
+
```vue
|
|
297
|
+
<template>
|
|
298
|
+
<div class="viewer-container">
|
|
299
|
+
<!-- 在此放入已配置数据的 OcrResultView -->
|
|
300
|
+
</div>
|
|
301
|
+
</template>
|
|
302
|
+
|
|
303
|
+
<style scoped>
|
|
304
|
+
.viewer-container {
|
|
305
|
+
height: 720px;
|
|
306
|
+
--viewer-bg: #111827;
|
|
307
|
+
--viewer-panel: #1f2937;
|
|
308
|
+
--viewer-border: #374151;
|
|
309
|
+
--viewer-card-text: #e5e7eb;
|
|
310
|
+
}
|
|
311
|
+
</style>
|
|
312
|
+
```
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
[data-v-61738111],[data-v-61738111]:before,[data-v-61738111]:after{box-sizing:border-box}button[data-v-61738111]{font:inherit;color:inherit;background:0 0;border:0;padding:0}h2[data-v-61738111],h3[data-v-61738111],p[data-v-61738111]{margin:0}svg[data-v-61738111]{vertical-align:middle;display:inline-block}.diff-cards[data-v-61738111]{flex-direction:column;gap:10px;display:flex}.diff-card[data-v-61738111]{--change-color:var(--diff-modify-dot,#f80);border:1px solid var(--viewer-border,#ffffff17);background:var(--diff-card-bg,#ffffff09);border-radius:12px;flex-shrink:0;min-width:0;padding:10px 12px;transition:border-color .15s,box-shadow .15s}.diff-card--insert[data-v-61738111]{--change-color:var(--diff-insert-dot,#00a870)}.diff-card--delete[data-v-61738111]{--change-color:var(--diff-delete-dot,#e34d59)}.diff-card[data-v-61738111]:hover{border-color:color-mix(in srgb, var(--viewer-card-text,#cbd5e1) 30%, transparent)}.diff-card.is-active[data-v-61738111]{border-color:var(--diff-active-border,#38bdf899);box-shadow:0 0 0 2px color-mix(in srgb, var(--diff-active-border,#38bdf899) 10%, transparent)}.diff-card.is-ignored[data-v-61738111]{--change-color:var(--ui-text-subtle,#64748b)}.card-heading[data-v-61738111]{align-items:center;gap:6px;display:flex}.card-select[data-v-61738111]{text-align:left;cursor:pointer;flex:1;align-items:center;gap:8px;min-width:0;display:flex}.type-icon[data-v-61738111]{width:24px;height:24px;color:var(--change-color);background:color-mix(in srgb, var(--change-color) 10%, transparent);border-radius:7px;flex-shrink:0;place-items:center;display:grid}.type-icon svg[data-v-61738111],.card-action svg[data-v-61738111]{width:14px;height:14px}.card-type[data-v-61738111]{color:var(--viewer-card-title,#e2e8f0);font-size:12px;font-weight:600}.card-number[data-v-61738111]{color:var(--ui-text-subtle,#64748b);font-variant-numeric:tabular-nums;font-size:10px}.card-action[data-v-61738111]{width:28px;height:28px;color:var(--ui-text-subtle,#64748b);cursor:pointer;border-radius:7px;flex-shrink:0;place-items:center;display:grid}.card-action[data-v-61738111]:hover{color:var(--viewer-card-title,#e2e8f0);background:var(--viewer-card,#ffffff09)}.card-action[data-v-61738111]:disabled{opacity:.35;cursor:wait}.card-action[data-v-61738111]:focus-visible,.card-select[data-v-61738111]:focus-visible{outline:2px solid var(--diff-active-border,#38bdf899);outline-offset:3px;border-radius:6px}.ignored-label[data-v-61738111]{color:var(--ui-text-subtle,#64748b);white-space:nowrap;font-size:10px}.card-content[data-v-61738111]{cursor:pointer}.text-row[data-v-61738111]{background:var(--viewer-card,#ffffff09);border-radius:7px;align-items:flex-start;gap:10px;margin-top:8px;padding:8px 10px;display:flex}.text-row--single[data-v-61738111]{background:color-mix(in srgb, var(--change-color) 6%, transparent)}.text-label[data-v-61738111]{color:var(--ui-text-subtle,#64748b);flex-shrink:0;padding-top:1px;font-size:10px;line-height:20px}.card-text[data-v-61738111]{min-width:0;color:var(--viewer-card-text,#cbd5e1);overflow-wrap:anywhere;white-space:pre-wrap;font-size:13px;line-height:1.65}.change-mark[data-v-61738111]{background:color-mix(in srgb, var(--change-color) 16%, transparent);border-radius:2px;font-weight:600}.card-location[data-v-61738111]{color:var(--ui-text-subtle,#64748b);margin:8px 0 0;font-size:10px;line-height:14px}[data-v-c65c6ace],[data-v-c65c6ace]:before,[data-v-c65c6ace]:after{box-sizing:border-box}button[data-v-c65c6ace]{font:inherit;color:inherit;background:0 0;border:0;padding:0}h2[data-v-c65c6ace],h3[data-v-c65c6ace],p[data-v-c65c6ace]{margin:0}svg[data-v-c65c6ace]{vertical-align:middle;display:inline-block}.ocr-result-view[data-v-c65c6ace]{flex-direction:column;height:100%;min-height:0;display:flex}.page-btn svg[data-v-c65c6ace]{width:14px;height:14px}.ocr-diff-viewer[data-v-c65c6ace]{min-height:0;color:var(--viewer-card-text,#cbd5e1);background:var(--viewer-bg,#0b1220);flex:1;font-family:Segoe UI,Microsoft YaHei,system-ui,sans-serif;display:flex}.viewer-main[data-v-c65c6ace]{flex-direction:column;flex:1;min-width:0;display:flex}.compare-row[data-v-c65c6ace]{background:var(--viewer-workspace,#080f1c);box-sizing:border-box;flex:1;gap:12px;min-height:0;padding:12px;display:flex;position:relative}.canvas-col[data-v-c65c6ace]{border:1px solid var(--viewer-border,#ffffff17);background:var(--viewer-panel,#111827);border-radius:10px;flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.canvas-info-bar[data-v-c65c6ace]{height:45px;color:var(--viewer-card-title,#e2e8f0);background:var(--viewer-toolbar,#111a2b);border-bottom:1px solid var(--viewer-border,#ffffff17);white-space:nowrap;text-overflow:ellipsis;flex-shrink:0;align-items:center;padding:6px 10px;font-size:12px;font-weight:600;display:flex;overflow:hidden}.canvas-info-bar-left[data-v-c65c6ace]{flex:1;align-items:center;gap:6px;min-width:0;display:flex}.canvas-info-bar-right[data-v-c65c6ace]{flex-shrink:0;align-items:center;gap:4px;margin-left:8px;display:flex}.page-num[data-v-c65c6ace]{color:var(--ui-text-muted,#94a3b8);text-align:center;-webkit-user-select:none;user-select:none;min-width:50px;font-size:12px}.page-btn[data-v-c65c6ace]{border:1px solid var(--viewer-border,#ffffff17);background:var(--viewer-card,#ffffff09);width:24px;height:24px;color:var(--ui-text-muted,#94a3b8);cursor:pointer;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:10px;line-height:1;transition:background .15s,border-color .15s;display:flex}.page-btn[data-v-c65c6ace]:hover:not(:disabled){color:#bfdbfe;background:#3b82f61f;border-color:#60a5fa52}.page-btn[data-v-c65c6ace]:disabled{opacity:.35;cursor:default}.canvas-info-bar-left-tag[data-v-c65c6ace]{color:#fff;background-color:#10b9812e;border:1px solid #34d39940;border-radius:6px;padding:2px 7px;font-size:12px;line-height:20px}.canvas-info-bar-left-tag.gray[data-v-c65c6ace]{color:var(--viewer-card-text,#cbd5e1);background-color:#94a3b824;border-color:#94a3b833}.canvas-info-bar-left-filename[data-v-c65c6ace]{white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.canvas-scroll[data-v-c65c6ace]{background:var(--viewer-scroll,#1a2230);flex:1;min-height:0;position:relative;overflow-y:scroll}.canvas-scroll--flip[data-v-c65c6ace]{direction:rtl}.virtual-spacer[data-v-c65c6ace]{direction:ltr;width:100%;position:relative}.viewport-canvas-stack[data-v-c65c6ace]{direction:ltr;width:100%;position:sticky;top:0}.compare-canvas[data-v-c65c6ace],.annotation-canvas[data-v-c65c6ace]{width:100%;display:block;position:absolute;inset:0}.compare-canvas[data-v-c65c6ace]{pointer-events:none}.annotation-canvas[data-v-c65c6ace]{z-index:1}.line-svg[data-v-c65c6ace]{pointer-events:none;position:absolute;top:0;left:0}.side-panel[data-v-c65c6ace]{border-left:1px solid var(--viewer-border,#ffffff17);background:var(--viewer-side,#0d1626);flex-direction:column;flex-shrink:0;width:clamp(300px,23vw,360px);min-height:0;display:flex;overflow:hidden}.review-header[data-v-c65c6ace]{flex-shrink:0;padding:18px 16px 12px}.review-title-row[data-v-c65c6ace]{justify-content:space-between;align-items:center;gap:12px;display:flex}.review-title-row h2[data-v-c65c6ace]{color:var(--viewer-card-title,#e2e8f0);align-items:center;gap:8px;font-size:14px;font-weight:650;display:flex}.review-title-row svg[data-v-c65c6ace]{width:16px;height:16px;color:var(--ui-text-subtle,#64748b)}.review-total[data-v-c65c6ace]{color:var(--ui-text-subtle,#64748b);font-variant-numeric:tabular-nums;font-size:11px}.review-segments[data-v-c65c6ace]{background:var(--viewer-card,#ffffff09);border-radius:9px;gap:3px;margin-top:16px;padding:3px;display:flex}.review-segments button[data-v-c65c6ace]{min-width:0;color:var(--ui-text-subtle,#64748b);white-space:nowrap;cursor:pointer;border-radius:7px;flex:1;justify-content:center;align-items:center;gap:6px;padding:7px 4px;font-size:12px;transition:background .15s,color .15s;display:flex}.review-segments button[aria-pressed=true][data-v-c65c6ace]{background:var(--viewer-side,#0d1626);color:var(--viewer-card-title,#e2e8f0);font-weight:600;box-shadow:0 1px 4px #00000014}.review-segments span[data-v-c65c6ace]{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.review-types[data-v-c65c6ace]{border-bottom:1px solid var(--viewer-border,#ffffff17);flex-shrink:0;align-items:center;gap:4px;padding:0 12px 12px;display:flex}.review-type[data-v-c65c6ace]{color:var(--ui-text-subtle,#64748b);white-space:nowrap;cursor:pointer;border-radius:6px;flex:1;justify-content:center;align-items:center;gap:4px;padding:5px 3px;font-size:11px;display:flex}.review-type span[data-v-c65c6ace]{opacity:.65;font-variant-numeric:tabular-nums;font-size:10px}.review-type[data-v-c65c6ace]:hover,.review-type[aria-pressed=true][data-v-c65c6ace]{color:var(--viewer-card-title,#e2e8f0);background:var(--viewer-card,#ffffff09)}.review-type[aria-pressed=true][data-v-c65c6ace]{font-weight:600}.side-panel[data-v-c65c6ace] .diff-cards{overscroll-behavior:contain;scrollbar-gutter:stable;background:color-mix(in srgb, var(--viewer-card,#ffffff09) 38%, var(--viewer-side,#0d1626));flex:1;min-height:0;padding:14px 12px 16px;overflow-y:auto}.review-footer[data-v-c65c6ace]{border-top:1px solid var(--viewer-border,#ffffff17);background:var(--viewer-side,#0d1626);flex-shrink:0;align-items:center;gap:8px;padding:12px;display:flex}.review-footer button[data-v-c65c6ace]{cursor:pointer;border-radius:8px;justify-content:center;align-items:center;gap:7px;min-width:0;padding:9px 10px;font-size:12px;transition:background .15s;display:flex}.review-footer svg[data-v-c65c6ace]{flex-shrink:0;width:14px;height:14px}.batch-ignore[data-v-c65c6ace]{color:var(--viewer-card-title,#e2e8f0);background:var(--viewer-card,#ffffff09);flex:1}.batch-ignore span[data-v-c65c6ace]{color:var(--ui-text-subtle,#64748b);font-variant-numeric:tabular-nums;font-size:10px}.batch-restore[data-v-c65c6ace]{color:var(--ui-text-subtle,#64748b)}.review-footer button[data-v-c65c6ace]:hover:not(:disabled){background:var(--diff-card-hover,#3b82f611);color:var(--viewer-card-title,#e2e8f0)}.review-footer button[data-v-c65c6ace]:disabled{opacity:.35;cursor:default}.side-panel button[data-v-c65c6ace]:focus-visible{outline:2px solid var(--diff-active-border,#38bdf899);outline-offset:2px}.review-empty[data-v-c65c6ace]{text-align:center;min-height:0;color:var(--ui-text-subtle,#64748b);flex:1;padding:42px 20px;overflow-y:auto}.empty-symbol[data-v-c65c6ace]{background:var(--viewer-card,#ffffff09);border-radius:14px;place-items:center;width:44px;height:44px;margin:0 auto 14px;display:grid}.empty-symbol svg[data-v-c65c6ace]{width:22px;height:22px}.empty-symbol.is-passed[data-v-c65c6ace]{color:var(--diff-insert-dot,#00a870);background:var(--diff-insert-bg,#34d3991a)}.review-empty h3[data-v-c65c6ace]{color:var(--viewer-card-title,#e2e8f0);font-size:14px;font-weight:600}.review-empty p[data-v-c65c6ace]{margin-top:8px;font-size:12px;line-height:1.6}.review-empty button[data-v-c65c6ace]{color:var(--viewer-card-text,#cbd5e1);cursor:pointer;text-underline-offset:4px;margin-top:20px;font-size:12px;text-decoration:underline}.page-loading[data-v-c65c6ace]{height:200px;color:var(--ui-text-muted,#94a3b8);justify-content:center;align-items:center;font-size:13px;display:flex}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types/index.js'
|