@koi-br/ocr-web-sdk 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 +754 -0
- package/dist/index.cjs.js +538 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +85525 -0
- package/dist/preview/PdfPreview.vue.d.ts +2 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/style.css +1 -0
- package/package.json +52 -0
- package/preview/ImagePreview.vue +235 -0
- package/preview/PdfPreview.vue +2571 -0
- package/preview/docxPreview.vue +216 -0
- package/preview/index.vue +317 -0
- package/preview/ofdPreview.vue +107 -0
- package/preview/tifPreview.vue +362 -0
- package/preview/xlsxPreview.vue +168 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="preview-container">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isLoading"
|
|
5
|
+
class="preview-loading-overlay"
|
|
6
|
+
>
|
|
7
|
+
<a-spin size="large" />
|
|
8
|
+
<p class="preview-loading-title">正在加载文档...</p>
|
|
9
|
+
</div>
|
|
10
|
+
<!-- 工具栏 -->
|
|
11
|
+
<div class="preview-toolbar preview-toolbar-right">
|
|
12
|
+
<div class="toolbar-group">
|
|
13
|
+
<a-tooltip
|
|
14
|
+
v-if="isDownload"
|
|
15
|
+
mini
|
|
16
|
+
position="bottom"
|
|
17
|
+
content="下载"
|
|
18
|
+
>
|
|
19
|
+
<a-button
|
|
20
|
+
size="small"
|
|
21
|
+
type="outline"
|
|
22
|
+
@click="emit('download')"
|
|
23
|
+
>
|
|
24
|
+
<Download :size="16" />
|
|
25
|
+
</a-button>
|
|
26
|
+
</a-tooltip>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<!-- 文档容器 -->
|
|
30
|
+
<div ref="bodyContainer" class="docx-body-container" />
|
|
31
|
+
<div ref="styleContainer" />
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup>
|
|
36
|
+
import { renderAsync } from 'docx-preview';
|
|
37
|
+
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
|
38
|
+
import { Download } from 'lucide-vue-next';
|
|
39
|
+
|
|
40
|
+
// 定义 props
|
|
41
|
+
const props = defineProps({
|
|
42
|
+
// 文档 URL 地址
|
|
43
|
+
url: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: true
|
|
46
|
+
},
|
|
47
|
+
// 文档名称
|
|
48
|
+
docName: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: '文档预览'
|
|
51
|
+
},
|
|
52
|
+
// 可选:直接传入文档数据(优先级高于 url)
|
|
53
|
+
docData: {
|
|
54
|
+
type: [Blob, ArrayBuffer],
|
|
55
|
+
default: null
|
|
56
|
+
},
|
|
57
|
+
// 缩放控制
|
|
58
|
+
minScale: {
|
|
59
|
+
type: Number,
|
|
60
|
+
default: 0.1
|
|
61
|
+
},
|
|
62
|
+
maxScale: {
|
|
63
|
+
type: Number,
|
|
64
|
+
default: 3
|
|
65
|
+
},
|
|
66
|
+
clickStep: {
|
|
67
|
+
type: Number,
|
|
68
|
+
default: 0.25
|
|
69
|
+
},
|
|
70
|
+
wheelStep: {
|
|
71
|
+
type: Number,
|
|
72
|
+
default: 0.1
|
|
73
|
+
},
|
|
74
|
+
isDownload: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const emit = defineEmits(['download']);
|
|
81
|
+
|
|
82
|
+
const containerRef = ref(null);
|
|
83
|
+
const isLoading = ref(false);
|
|
84
|
+
const error = ref('');
|
|
85
|
+
const bodyContainer = ref(null);
|
|
86
|
+
const styleContainer = ref(null);
|
|
87
|
+
|
|
88
|
+
// 新增状态管理
|
|
89
|
+
const scale = ref(1);
|
|
90
|
+
const rotation = ref(0);
|
|
91
|
+
|
|
92
|
+
// 缩放相关逻辑
|
|
93
|
+
const zoom = (delta, isWheel = false) => {
|
|
94
|
+
const step = isWheel ? props.wheelStep : props.clickStep;
|
|
95
|
+
const newScale = scale.value + delta * step;
|
|
96
|
+
if (newScale <= props.minScale) {
|
|
97
|
+
scale.value = props.minScale;
|
|
98
|
+
} else if (newScale >= props.maxScale) {
|
|
99
|
+
scale.value = props.maxScale;
|
|
100
|
+
} else {
|
|
101
|
+
scale.value = newScale;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// 旋转处理
|
|
106
|
+
const rotate = delta => {
|
|
107
|
+
rotation.value = (rotation.value + delta + 360) % 360;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// 滚轮处理函数
|
|
111
|
+
const handleWheel = e => {
|
|
112
|
+
if (e.ctrlKey || e.metaKey) {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
const delta = e.deltaY > 0 ? -1 : 1;
|
|
115
|
+
zoom(delta, true);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// 重置功能
|
|
120
|
+
const reset = () => {
|
|
121
|
+
scale.value = 1;
|
|
122
|
+
rotation.value = 0;
|
|
123
|
+
if (containerRef.value) {
|
|
124
|
+
containerRef.value.scrollTop = 0;
|
|
125
|
+
containerRef.value.scrollLeft = 0;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// 渲染文档的方法
|
|
130
|
+
const renderDocument = async data => {
|
|
131
|
+
try {
|
|
132
|
+
if (!bodyContainer.value) {
|
|
133
|
+
throw new Error('bodyContainer 元素未找到');
|
|
134
|
+
}
|
|
135
|
+
bodyContainer.value.innerHTML = '';
|
|
136
|
+
const result = await renderAsync(
|
|
137
|
+
data,
|
|
138
|
+
bodyContainer.value,
|
|
139
|
+
styleContainer.value,
|
|
140
|
+
{
|
|
141
|
+
inWrapper: true,
|
|
142
|
+
styleMap: null,
|
|
143
|
+
defaultStyleMap: true,
|
|
144
|
+
useBase64URL: true,
|
|
145
|
+
ignoreHeight: false,
|
|
146
|
+
ignoreWidth: false,
|
|
147
|
+
ignoreFonts: false,
|
|
148
|
+
breakPages: true
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
error.value = `文档渲染失败: ${err.message}`;
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 加载文档数据
|
|
158
|
+
const loadDocument = async () => {
|
|
159
|
+
try {
|
|
160
|
+
isLoading.value = true;
|
|
161
|
+
error.value = '';
|
|
162
|
+
let documentData = null;
|
|
163
|
+
if (props.url) {
|
|
164
|
+
// 通过 URL 请求获取文档数据
|
|
165
|
+
const response = await fetch(props.url);
|
|
166
|
+
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
169
|
+
}
|
|
170
|
+
documentData = await response.arrayBuffer();
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error('未提供文档 URL 或数据');
|
|
173
|
+
}
|
|
174
|
+
isLoading.value = false;
|
|
175
|
+
await nextTick();
|
|
176
|
+
// 渲染文档
|
|
177
|
+
await renderDocument(documentData);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error('文档加载失败:', err);
|
|
180
|
+
error.value = `加载文档失败: ${err.message}`;
|
|
181
|
+
isLoading.value = false;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
watch(
|
|
186
|
+
[() => props.url, () => props.docData],
|
|
187
|
+
() => {
|
|
188
|
+
if (props.url || props.docData) {
|
|
189
|
+
reset();
|
|
190
|
+
loadDocument();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{ immediate: true }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// 组件挂载后加载文档
|
|
197
|
+
onMounted(() => {
|
|
198
|
+
if (props.url || props.docData) {
|
|
199
|
+
loadDocument();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// 暴露方法给父组件使用
|
|
204
|
+
defineExpose({
|
|
205
|
+
reset,
|
|
206
|
+
zoom,
|
|
207
|
+
rotate
|
|
208
|
+
});
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<style lang="less" scoped>
|
|
212
|
+
// 样式已统一到公共样式文件,这里只保留组件特定样式
|
|
213
|
+
:deep(.docx-wrapper) {
|
|
214
|
+
background-color: #fff;
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="file-preview-container">
|
|
3
|
+
<!-- 下载进度条 - 居中显示 -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="downloading"
|
|
6
|
+
class="preview-loading-overlay"
|
|
7
|
+
>
|
|
8
|
+
<div class="preview-loading-content">
|
|
9
|
+
<div class="preview-loading-title">
|
|
10
|
+
正在加载文件
|
|
11
|
+
<div class="preview-loading-filename">{{ currentFileName }}</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="preview-progress-bar">
|
|
14
|
+
<div
|
|
15
|
+
class="preview-progress-fill"
|
|
16
|
+
:style="{ width: `${progress}%` }"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="preview-progress-text">
|
|
20
|
+
{{ progress }}%
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- PDF预览 -->
|
|
26
|
+
<PdfPreview
|
|
27
|
+
v-if="fileType === 'pdf'"
|
|
28
|
+
ref="pdfPreviewRef"
|
|
29
|
+
:pdf-url="fileUrl"
|
|
30
|
+
:is-download="props.isDownload"
|
|
31
|
+
@download="handleDownload"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<!-- 图片预览 -->
|
|
35
|
+
<ImagePreview
|
|
36
|
+
v-else-if="fileType === 'image'"
|
|
37
|
+
ref="imagePreviewRef"
|
|
38
|
+
:url="fileUrl"
|
|
39
|
+
:original-id="originalId"
|
|
40
|
+
:is-download="props.isDownload"
|
|
41
|
+
@original="handleOriginal"
|
|
42
|
+
@download="handleDownload"
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<!-- ttf预览 -->
|
|
46
|
+
<TifPreview
|
|
47
|
+
v-else-if="fileType === 'tif'"
|
|
48
|
+
ref="tifPreviewRe"
|
|
49
|
+
:url="fileUrl"
|
|
50
|
+
:is-download="props.isDownload"
|
|
51
|
+
@download="handleDownload"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<!-- docx预览 -->
|
|
55
|
+
<DocxPreview
|
|
56
|
+
v-else-if="fileType === 'docx'"
|
|
57
|
+
ref="docxPreviewRef"
|
|
58
|
+
:url="fileUrl"
|
|
59
|
+
:is-download="props.isDownload"
|
|
60
|
+
@download="handleDownload"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<!-- OFD预览 -->
|
|
64
|
+
<OfdPreview
|
|
65
|
+
v-else-if="fileType === 'ofd'"
|
|
66
|
+
ref="ofdPreviewRef"
|
|
67
|
+
:url="fileUrl"
|
|
68
|
+
:is-download="props.isDownload"
|
|
69
|
+
@download="handleDownload"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<!-- xlsx预览 -->
|
|
73
|
+
<XLSXPreview
|
|
74
|
+
v-else-if="fileType === 'xlsx'"
|
|
75
|
+
ref="xlsxPreviewRef"
|
|
76
|
+
:url="fileUrl"
|
|
77
|
+
:is-download="props.isDownload"
|
|
78
|
+
@download="handleDownload"
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
<!-- 不支持的文件类型 -->
|
|
82
|
+
<div v-else class="preview-empty-container">
|
|
83
|
+
<div class="preview-empty-message">{{ unsupportedMessage }}</div>
|
|
84
|
+
<a-button
|
|
85
|
+
v-if="downloadUrl"
|
|
86
|
+
type="primary"
|
|
87
|
+
@click="handleDownload"
|
|
88
|
+
>
|
|
89
|
+
点击下载文件
|
|
90
|
+
</a-button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script setup>
|
|
96
|
+
import { ref, onBeforeUnmount } from 'vue';
|
|
97
|
+
import { Message } from '@arco-design/web-vue';
|
|
98
|
+
import ImagePreview from './ImagePreview.vue';
|
|
99
|
+
import PdfPreview from './PdfPreview.vue';
|
|
100
|
+
import TifPreview from './tifPreview.vue';
|
|
101
|
+
import OfdPreview from './ofdPreview.vue';
|
|
102
|
+
import DocxPreview from './docxPreview.vue';
|
|
103
|
+
import XLSXPreview from './xlsxPreview.vue';
|
|
104
|
+
|
|
105
|
+
// 定义 props
|
|
106
|
+
const props = defineProps({
|
|
107
|
+
isDownload: {
|
|
108
|
+
type: Boolean,
|
|
109
|
+
default: false
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const fileType = ref('');
|
|
114
|
+
const currentFileName = ref('');
|
|
115
|
+
const fileUrl = ref('');
|
|
116
|
+
const downloading = ref(false);
|
|
117
|
+
const progress = ref(0);
|
|
118
|
+
const unsupportedMessage = ref('暂无预览内容');
|
|
119
|
+
const downloadUrl = ref('');
|
|
120
|
+
const imagePreviewRef = ref(null);
|
|
121
|
+
const pdfPreviewRef = ref(null);
|
|
122
|
+
const tifPreviewRef = ref(null);
|
|
123
|
+
const ofdPreviewRef = ref(null);
|
|
124
|
+
const docxPreviewRef = ref(null);
|
|
125
|
+
const xlsxPreviewRef = ref(null);
|
|
126
|
+
|
|
127
|
+
const originalId = ref('');
|
|
128
|
+
|
|
129
|
+
// 添加 AbortController 的引用
|
|
130
|
+
const abortController = ref(null);
|
|
131
|
+
|
|
132
|
+
// 通过文件名判断类型
|
|
133
|
+
const getFileType = (fileName = '') => {
|
|
134
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
135
|
+
if (ext === 'pdf') return 'pdf';
|
|
136
|
+
if (['tif'].includes(ext)) return 'tif';
|
|
137
|
+
if (['ofd'].includes(ext)) return 'ofd';
|
|
138
|
+
if (['docx'].includes(ext)) return 'docx';
|
|
139
|
+
if (['xlsx'].includes(ext)) return 'xlsx';
|
|
140
|
+
if (
|
|
141
|
+
[
|
|
142
|
+
'jpg',
|
|
143
|
+
'jpeg',
|
|
144
|
+
'png',
|
|
145
|
+
'bmp',
|
|
146
|
+
'gif',
|
|
147
|
+
'webp',
|
|
148
|
+
'svg',
|
|
149
|
+
'apng',
|
|
150
|
+
'avif'
|
|
151
|
+
].includes(ext)
|
|
152
|
+
)
|
|
153
|
+
return 'image';
|
|
154
|
+
return 'unknown';
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// 获取文件的MIME类型
|
|
158
|
+
const getMimeType = (fileName = '') => {
|
|
159
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
160
|
+
const mimeTypes = {
|
|
161
|
+
pdf: 'application/pdf',
|
|
162
|
+
tif: 'image/tif',
|
|
163
|
+
docx: 'application/docx',
|
|
164
|
+
jpg: 'image/jpeg',
|
|
165
|
+
jpeg: 'image/jpeg',
|
|
166
|
+
bmp: 'image/bmp',
|
|
167
|
+
apng: 'image/apng',
|
|
168
|
+
avif: 'image/avif',
|
|
169
|
+
png: 'image/png',
|
|
170
|
+
gif: 'image/gif',
|
|
171
|
+
webp: 'image/webp',
|
|
172
|
+
svg: 'image/svg+xml',
|
|
173
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
174
|
+
xls: 'application/vnd.ms-excel'
|
|
175
|
+
};
|
|
176
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// 清除预览
|
|
180
|
+
const clearPreview = () => {
|
|
181
|
+
// 取消之前的请求
|
|
182
|
+
if (abortController.value) {
|
|
183
|
+
abortController.value.abort();
|
|
184
|
+
abortController.value = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (fileUrl.value) {
|
|
188
|
+
URL.revokeObjectURL(fileUrl.value);
|
|
189
|
+
fileUrl.value = '';
|
|
190
|
+
}
|
|
191
|
+
fileType.value = '';
|
|
192
|
+
downloading.value = false;
|
|
193
|
+
progress.value = 0;
|
|
194
|
+
unsupportedMessage.value = '暂无预览内容';
|
|
195
|
+
|
|
196
|
+
// 重置图片预览组件
|
|
197
|
+
if (imagePreviewRef.value) {
|
|
198
|
+
imagePreviewRef.value.reset();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 重置 PDF 预览组件
|
|
202
|
+
if (pdfPreviewRef.value) {
|
|
203
|
+
pdfPreviewRef.value.cleanup();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 重置 TIFF 预览组件
|
|
207
|
+
if (tifPreviewRef.value) {
|
|
208
|
+
tifPreviewRef.value.cleanup();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 重置 OFD 预览组件
|
|
212
|
+
if (ofdPreviewRef.value) {
|
|
213
|
+
ofdPreviewRef.value.cleanup();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 重置 DOCX 预览组件
|
|
217
|
+
if (docxPreviewRef.value) {
|
|
218
|
+
docxPreviewRef.value.reset?.();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 重置 XLSX 预览组件
|
|
222
|
+
if (xlsxPreviewRef.value) {
|
|
223
|
+
xlsxPreviewRef.value.cleanup?.();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// 添加下载处理函数
|
|
228
|
+
const handleDownload = () => {
|
|
229
|
+
if (!downloadUrl.value) return;
|
|
230
|
+
const link = document.createElement('a');
|
|
231
|
+
link.href = downloadUrl.value;
|
|
232
|
+
link.download = currentFileName.value;
|
|
233
|
+
document.body.appendChild(link);
|
|
234
|
+
link.click();
|
|
235
|
+
document.body.removeChild(link);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 预览方法 - SDK模式
|
|
240
|
+
* @param {string|Blob|File} file - 文件URL、Blob对象或File对象
|
|
241
|
+
* @param {string} fileName - 文件名(可选,用于判断文件类型)
|
|
242
|
+
* @param {object} options - 可选配置
|
|
243
|
+
* @param {Function} options.onProgress - 下载进度回调函数 (progress) => void
|
|
244
|
+
* @param {string} options.originalId - 原图ID(用于图片预览)
|
|
245
|
+
*/
|
|
246
|
+
const preview = async (file, fileName, options = {}) => {
|
|
247
|
+
if (!file) {
|
|
248
|
+
Message.warning('请提供文件');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const { onProgress, originalId: originalFileId } = options;
|
|
253
|
+
|
|
254
|
+
// 清除旧的预览
|
|
255
|
+
clearPreview();
|
|
256
|
+
|
|
257
|
+
// 如果传入的是URL字符串
|
|
258
|
+
if (typeof file === 'string') {
|
|
259
|
+
fileUrl.value = file;
|
|
260
|
+
downloadUrl.value = file;
|
|
261
|
+
currentFileName.value = fileName || '文件预览';
|
|
262
|
+
originalId.value = originalFileId || '';
|
|
263
|
+
|
|
264
|
+
// 判断文件类型
|
|
265
|
+
const type = getFileType(fileName || file);
|
|
266
|
+
if (type === 'unknown') {
|
|
267
|
+
unsupportedMessage.value = `暂不支持 ${fileName || file} 文件预览`;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
fileType.value = type;
|
|
271
|
+
downloading.value = false;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 如果传入的是Blob或File对象
|
|
276
|
+
if (file instanceof Blob || file instanceof File) {
|
|
277
|
+
currentFileName.value = fileName || (file instanceof File ? file.name : '文件预览');
|
|
278
|
+
originalId.value = originalFileId || '';
|
|
279
|
+
|
|
280
|
+
// 判断文件类型
|
|
281
|
+
const type = getFileType(currentFileName.value);
|
|
282
|
+
if (type === 'unknown') {
|
|
283
|
+
unsupportedMessage.value = `暂不支持 ${currentFileName.value} 文件预览`;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
fileType.value = type;
|
|
288
|
+
fileUrl.value = URL.createObjectURL(file);
|
|
289
|
+
downloading.value = false;
|
|
290
|
+
progress.value = 100;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Message.error('不支持的文件格式');
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const handleOriginal = () => {
|
|
298
|
+
if (fileUrl.value) {
|
|
299
|
+
preview(fileUrl.value, currentFileName.value, { originalId: fileUrl.value });
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// 暴露方法给父组件
|
|
304
|
+
defineExpose({
|
|
305
|
+
preview,
|
|
306
|
+
clearPreview
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// 组件销毁时清理资源
|
|
310
|
+
onBeforeUnmount(() => {
|
|
311
|
+
clearPreview();
|
|
312
|
+
});
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<style lang="less" scoped>
|
|
316
|
+
// 样式已统一到公共样式文件,无需额外样式
|
|
317
|
+
</style>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="preview-container">
|
|
3
|
+
<!-- 工具栏 -->
|
|
4
|
+
<div v-if="props.isDownload" class="preview-toolbar preview-toolbar-right">
|
|
5
|
+
<div class="toolbar-group">
|
|
6
|
+
<a-tooltip
|
|
7
|
+
mini
|
|
8
|
+
position="bottom"
|
|
9
|
+
content="下载"
|
|
10
|
+
>
|
|
11
|
+
<a-button
|
|
12
|
+
size="small"
|
|
13
|
+
type="outline"
|
|
14
|
+
@click="emit('download')"
|
|
15
|
+
>
|
|
16
|
+
<Download :size="16" />
|
|
17
|
+
</a-button>
|
|
18
|
+
</a-tooltip>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<OfdView
|
|
22
|
+
:show-open-file-button="false"
|
|
23
|
+
:ofd-link="props.url"
|
|
24
|
+
class="ofd-view"
|
|
25
|
+
@load="handleDocumentLoad"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import { ref, watch, onMounted } from 'vue';
|
|
32
|
+
import { OfdView } from 'bestofdview';
|
|
33
|
+
import 'bestofdview/dist/style.css';
|
|
34
|
+
import { Message } from '@arco-design/web-vue';
|
|
35
|
+
import { Download } from 'lucide-vue-next';
|
|
36
|
+
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
url: {
|
|
39
|
+
type: String,
|
|
40
|
+
required: true
|
|
41
|
+
},
|
|
42
|
+
isDownload: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits(['download']);
|
|
49
|
+
|
|
50
|
+
const loading = ref(false);
|
|
51
|
+
|
|
52
|
+
// 监听文档加载状态
|
|
53
|
+
const handleDocumentLoad = () => {
|
|
54
|
+
loading.value = true;
|
|
55
|
+
// 使用 fetch 检查文件是否可访问
|
|
56
|
+
if (props.url) {
|
|
57
|
+
fetch(props.url)
|
|
58
|
+
.then(response => {
|
|
59
|
+
if (response.ok) {
|
|
60
|
+
// 文件可访问,等待 bestofdview 内部加载完成
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
loading.value = false;
|
|
63
|
+
}, 1000);
|
|
64
|
+
} else {
|
|
65
|
+
loading.value = false;
|
|
66
|
+
Message.error(`加载失败: ${response.status}`);
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.catch(error => {
|
|
70
|
+
console.error('检查OFD文件失败:', error);
|
|
71
|
+
loading.value = false;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 监听URL变化
|
|
77
|
+
watch(
|
|
78
|
+
() => props.url,
|
|
79
|
+
newUrl => {
|
|
80
|
+
if (newUrl) {
|
|
81
|
+
handleDocumentLoad();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{ immediate: true }
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// 组件挂载时处理
|
|
88
|
+
onMounted(() => {
|
|
89
|
+
if (props.url) {
|
|
90
|
+
handleDocumentLoad();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 清理函数(保持接口兼容性)
|
|
95
|
+
const cleanup = () => {
|
|
96
|
+
loading.value = false;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// 暴露清理方法给父组件
|
|
100
|
+
defineExpose({
|
|
101
|
+
cleanup
|
|
102
|
+
});
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<style lang="less" scoped>
|
|
106
|
+
// 样式已统一到公共样式文件,这里只保留组件特定样式
|
|
107
|
+
</style>
|