@jrchan/office-preview 1.0.5 → 1.0.6
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 +274 -12
- package/dist/office-preview.css +1 -1
- package/dist/office-preview.es.js +4 -4
- package/dist/office-preview.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,287 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Office Preview 组件
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
基于 Vue 3 的在线文档预览组件,支持 docx、pptx、excel、pdf 等格式。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## ⚠️ 重要说明
|
|
6
|
+
|
|
7
|
+
**本组件是一个纯粹的 UI 组件,不包含任何测试数据或硬编码的文件 URL。**
|
|
8
|
+
|
|
9
|
+
- ✅ `App.vue` 仅用于本地开发调试,**不会被打包到 npm 包中**
|
|
10
|
+
- ✅ 使用方项目必须自己传入 `file-url` 和 `file-type` 参数
|
|
11
|
+
- ✅ 组件不会主动加载任何测试文件
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @jrchan/office-preview
|
|
7
17
|
```
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
## 使用方法
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
### 方式一:全局注册(推荐)
|
|
22
|
+
|
|
23
|
+
**main.ts**
|
|
24
|
+
```typescript
|
|
25
|
+
import { createApp } from 'vue'
|
|
26
|
+
import App from './App.vue'
|
|
27
|
+
import OfficePreview from '@jrchan/office-preview'
|
|
28
|
+
|
|
29
|
+
const app = createApp(App)
|
|
30
|
+
app.use(OfficePreview)
|
|
31
|
+
app.mount('#app')
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**在组件中使用**
|
|
35
|
+
```vue
|
|
36
|
+
<template>
|
|
37
|
+
<div class="app">
|
|
38
|
+
<!-- ✅ 必须传入 file-url 和 file-type -->
|
|
39
|
+
<OfficePreview
|
|
40
|
+
:file-url="myFileUrl"
|
|
41
|
+
:file-type="myFileType"
|
|
42
|
+
@rendered="handleRendered"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
// ✅ 使用方自己提供文件 URL(可以是 HTTP/HTTPS 地址或 Blob URL)
|
|
51
|
+
const myFileUrl = ref('https://your-server.com/files/document.docx')
|
|
52
|
+
const myFileType = ref('docx')
|
|
53
|
+
|
|
54
|
+
const handleRendered = (type: string) => {
|
|
55
|
+
console.log(`${type} 文件渲染完成`)
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 方式二:按需引入
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<template>
|
|
64
|
+
<OfficePreview
|
|
65
|
+
:file-url="fileUrl"
|
|
66
|
+
:file-type="fileType"
|
|
67
|
+
height="600px"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { OfficePreview } from '@jrchan/office-preview'
|
|
73
|
+
|
|
74
|
+
// ✅ 文件 URL 由使用方提供
|
|
75
|
+
const fileUrl = 'https://cdn.example.com/report.xlsx'
|
|
76
|
+
const fileType = 'xlsx'
|
|
77
|
+
</script>
|
|
13
78
|
```
|
|
14
79
|
|
|
15
|
-
|
|
80
|
+
## Props 参数
|
|
81
|
+
|
|
82
|
+
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
|
83
|
+
|--------|------|------|--------|------|
|
|
84
|
+
| file-url | `string` | ✅ 是 | `''` | 文件 URL 地址(支持 http/https/blob URL) |
|
|
85
|
+
| file-type | `string` | ✅ 是 | `''` | 文件类型(doc/docx/xls/xlsx/ppt/pptx/pdf) |
|
|
86
|
+
| height | `string` | ❌ 否 | `'100%'` | 预览区域高度 |
|
|
87
|
+
|
|
88
|
+
## Events 事件
|
|
89
|
+
|
|
90
|
+
| 事件名 | 回调参数 | 说明 |
|
|
91
|
+
|--------|----------|------|
|
|
92
|
+
| rendered | `(type: string)` | 文件渲染完成时触发 |
|
|
93
|
+
|
|
94
|
+
## 完整示例
|
|
95
|
+
|
|
96
|
+
### 示例 1:从服务器加载文件
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<template>
|
|
100
|
+
<OfficePreview
|
|
101
|
+
:file-url="documentUrl"
|
|
102
|
+
:file-type="documentType"
|
|
103
|
+
/>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<script setup lang="ts">
|
|
107
|
+
import { ref } from 'vue'
|
|
108
|
+
|
|
109
|
+
const documentUrl = ref('https://example.com/contracts/2024.docx')
|
|
110
|
+
const documentType = ref('docx')
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 示例 2:上传文件后预览
|
|
115
|
+
|
|
116
|
+
```vue
|
|
117
|
+
<template>
|
|
118
|
+
<div>
|
|
119
|
+
<input type="file" @change="handleFileChange" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" />
|
|
120
|
+
<OfficePreview
|
|
121
|
+
v-if="fileUrl"
|
|
122
|
+
:file-url="fileUrl"
|
|
123
|
+
:file-type="fileType"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
127
|
+
|
|
128
|
+
<script setup lang="ts">
|
|
129
|
+
import { ref } from 'vue'
|
|
130
|
+
import { OfficePreview } from '@jrchan/office-preview'
|
|
131
|
+
|
|
132
|
+
const fileUrl = ref<string>('')
|
|
133
|
+
const fileType = ref<string>('')
|
|
134
|
+
|
|
135
|
+
const handleFileChange = (e: Event) => {
|
|
136
|
+
const target = e.target as HTMLInputElement
|
|
137
|
+
const file = target.files?.[0]
|
|
138
|
+
if (file) {
|
|
139
|
+
// 创建 Blob URL
|
|
140
|
+
fileUrl.value = URL.createObjectURL(file)
|
|
141
|
+
fileType.value = file.name // 组件会根据文件名自动识别类型
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 示例 3:文件列表切换预览
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<template>
|
|
151
|
+
<div class="document-viewer">
|
|
152
|
+
<ul>
|
|
153
|
+
<li v-for="file in fileList" :key="file.id" @click="selectFile(file)">
|
|
154
|
+
{{ file.name }}
|
|
155
|
+
</li>
|
|
156
|
+
</ul>
|
|
157
|
+
|
|
158
|
+
<OfficePreview
|
|
159
|
+
v-if="selectedFile"
|
|
160
|
+
:file-url="selectedFile.url"
|
|
161
|
+
:file-type="selectedFile.type"
|
|
162
|
+
height="600px"
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
</template>
|
|
166
|
+
|
|
167
|
+
<script setup lang="ts">
|
|
168
|
+
import { ref } from 'vue'
|
|
169
|
+
import { OfficePreview } from '@jrchan/office-preview'
|
|
16
170
|
|
|
17
|
-
|
|
171
|
+
interface FileItem {
|
|
172
|
+
id: number
|
|
173
|
+
name: string
|
|
174
|
+
url: string
|
|
175
|
+
type: string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const fileList = ref<FileItem[]>([
|
|
179
|
+
{ id: 1, name: '合同.docx', url: 'https://cdn.example.com/contract.docx', type: 'docx' },
|
|
180
|
+
{ id: 2, name: '报表.xlsx', url: 'https://cdn.example.com/report.xlsx', type: 'xlsx' },
|
|
181
|
+
{ id: 3, name: '演示.pptx', url: 'https://cdn.example.com/demo.pptx', type: 'pptx' },
|
|
182
|
+
])
|
|
183
|
+
|
|
184
|
+
const selectedFile = ref<FileItem | null>(null)
|
|
185
|
+
|
|
186
|
+
const selectFile = (file: FileItem) => {
|
|
187
|
+
selectedFile.value = file
|
|
188
|
+
}
|
|
189
|
+
</script>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## ⚠️ CSP 配置说明
|
|
193
|
+
|
|
194
|
+
本组件使用 Web Worker 处理文档解析,如遇到 CSP 报错,请在使用方项目中添加以下配置:
|
|
195
|
+
|
|
196
|
+
### Nginx 配置
|
|
197
|
+
```nginx
|
|
198
|
+
add_header Content-Security-Policy "worker-src 'self' blob: data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:;";
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### HTML Meta 标签
|
|
202
|
+
```html
|
|
203
|
+
<meta http-equiv="Content-Security-Policy"
|
|
204
|
+
content="worker-src 'self' blob: data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:;">
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Vite 开发环境
|
|
208
|
+
```typescript
|
|
209
|
+
// vite.config.ts
|
|
210
|
+
export default defineConfig({
|
|
211
|
+
server: {
|
|
212
|
+
headers: {
|
|
213
|
+
'Content-Security-Policy': "worker-src 'self' blob: data:;",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 支持的文档格式
|
|
220
|
+
|
|
221
|
+
| 格式类型 | 扩展名 | 使用的组件 |
|
|
222
|
+
|----------|--------|-----------|
|
|
223
|
+
| PDF | `.pdf` | @vue-office/pdf |
|
|
224
|
+
| Word | `.doc`, `.docx` | @vue-office/docx |
|
|
225
|
+
| Excel | `.xls`, `.xlsx`, `.csv` | @vue-office/excel |
|
|
226
|
+
| PowerPoint | `.ppt`, `.pptx` | @vue-office/pptx |
|
|
227
|
+
|
|
228
|
+
## 常见问题
|
|
229
|
+
|
|
230
|
+
### Q1: 为什么组件会加载 `/file/234.docx`?
|
|
231
|
+
|
|
232
|
+
**A:** 这是误解。组件**不会**加载任何测试文件。请检查:
|
|
233
|
+
|
|
234
|
+
1. ✅ 是否正确传入了 `file-url` 参数
|
|
235
|
+
2. ✅ 参数值是否为你自己的文件 URL
|
|
236
|
+
3. ✅ 是否误将本地测试代码复制到了使用方项目
|
|
237
|
+
|
|
238
|
+
组件的 `App.vue` 仅用于本地开发调试,**不会被打包到 npm 包中**。
|
|
239
|
+
|
|
240
|
+
### Q2: 不传参数会怎样?
|
|
241
|
+
|
|
242
|
+
**A:** 如果不传入 `file-url` 和 `file-type`,组件将显示"请选择文件进行预览"提示,不会加载任何文件。
|
|
243
|
+
|
|
244
|
+
### Q3: 如何查看构建产物确认没有 App.vue?
|
|
245
|
+
|
|
246
|
+
**A:** 执行以下命令:
|
|
247
|
+
```bash
|
|
248
|
+
npm run build
|
|
249
|
+
grep -r "234.docx" dist/ # 应该无结果
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 开发指南
|
|
253
|
+
|
|
254
|
+
### 本地开发调试
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# 安装依赖
|
|
258
|
+
pnpm install
|
|
259
|
+
|
|
260
|
+
# 启动开发服务器(访问 localhost:5173 查看测试页面)
|
|
261
|
+
pnpm dev
|
|
262
|
+
|
|
263
|
+
# 构建生产包
|
|
18
264
|
pnpm build
|
|
265
|
+
|
|
266
|
+
# 发布到 npm
|
|
267
|
+
npm publish --access public
|
|
19
268
|
```
|
|
20
269
|
|
|
21
|
-
###
|
|
270
|
+
### 项目结构
|
|
22
271
|
|
|
23
|
-
```sh
|
|
24
|
-
pnpm lint
|
|
25
272
|
```
|
|
273
|
+
@jrchan/office-preview/
|
|
274
|
+
├── src/
|
|
275
|
+
│ ├── components/
|
|
276
|
+
│ │ └── office-preview.vue # ✅ 核心组件(会被打包)
|
|
277
|
+
│ ├── index.ts # ✅ 导出入口(会被打包)
|
|
278
|
+
│ ├── App.vue # ❌ 仅本地测试(不会打包)
|
|
279
|
+
│ └── main.ts # ❌ 仅本地测试(不会打包)
|
|
280
|
+
├── dist/ # 构建输出(发布到 npm)
|
|
281
|
+
├── package.json
|
|
282
|
+
└── vite.config.ts
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
package/dist/office-preview.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.file-preview-container[data-v-
|
|
1
|
+
.file-preview-container[data-v-a3fccd66],.preview-area[data-v-a3fccd66]{height:100%}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent as f, ref as a, watch as u, onMounted as v, openBlock as
|
|
1
|
+
import { defineComponent as f, ref as a, watch as u, onMounted as v, openBlock as d, createElementBlock as r, createElementVNode as s, toDisplayString as m, unref as _, createCommentVNode as x } from "vue";
|
|
2
2
|
const g = { class: "file-preview-container" }, w = { class: "preview-area" }, y = {
|
|
3
3
|
key: 0,
|
|
4
4
|
class: "loading-mask"
|
|
@@ -23,12 +23,12 @@ const g = { class: "file-preview-container" }, w = { class: "preview-area" }, y
|
|
|
23
23
|
}, {
|
|
24
24
|
immediate: !0
|
|
25
25
|
}), v(async () => {
|
|
26
|
-
}), (o, t) => (
|
|
26
|
+
}), (o, t) => (d(), r("div", g, [
|
|
27
27
|
s("div", w, [
|
|
28
28
|
t[0] || (t[0] = s("div", null, "在线预览", -1)),
|
|
29
29
|
s("div", null, m(l.fileUrl), 1)
|
|
30
30
|
]),
|
|
31
|
-
_(c) ? (
|
|
31
|
+
_(c) ? (d(), r("div", y, [...t[1] || (t[1] = [
|
|
32
32
|
s("div", { class: "loading-content" }, [
|
|
33
33
|
s("div", { class: "spinner" }),
|
|
34
34
|
s("p", null, "正在加载文件...")
|
|
@@ -41,7 +41,7 @@ const g = { class: "file-preview-container" }, w = { class: "preview-area" }, y
|
|
|
41
41
|
for (const [c, i] of n)
|
|
42
42
|
e[c] = i;
|
|
43
43
|
return e;
|
|
44
|
-
}, C = /* @__PURE__ */ k(h, [["__scopeId", "data-v-
|
|
44
|
+
}, C = /* @__PURE__ */ k(h, [["__scopeId", "data-v-a3fccd66"]]), E = [C], O = (l) => {
|
|
45
45
|
E.forEach((n) => {
|
|
46
46
|
l.component(n.name, n);
|
|
47
47
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(o,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(o=typeof globalThis<"u"?globalThis:o||self,e(o.OfficePreview={},o.Vue))})(this,(function(o,e){"use strict";const f={class:"file-preview-container"},a={class:"preview-area"},p={key:0,class:"loading-mask"},r=((l,s)=>{const t=l.__vccOpts||l;for(const[d,c]of s)t[d]=c;return t})(e.defineComponent({name:"OfficePreview",__name:"office-preview",props:{fileUrl:{default:""},fileType:{default:""},height:{default:"100%"}},emits:["rendered"],setup(l,{emit:s}){let t=e.ref("pdf"),d=e.ref(!1);const c=l,_=i=>{console.log("fileName",i);const n=i.toLowerCase().split(".").pop();["pdf"].includes(n)?t.value="pdf":["doc","docx"].includes(n)?t.value="doc":["xls","xlsx","csv"].includes(n)?t.value="xls":["ppt","pptx"].includes(n)?(t.value="ppt",console.log("pptxPrviewer",i)):t.value="other"};return e.watch([()=>c.fileType,()=>c.fileUrl],i=>{t.value=i[0],_(i[1])},{immediate:!0}),e.onMounted(async()=>{}),(i,n)=>(e.openBlock(),e.createElementBlock("div",f,[e.createElementVNode("div",a,[n[0]||(n[0]=e.createElementVNode("div",null,"在线预览",-1)),e.createElementVNode("div",null,e.toDisplayString(l.fileUrl),1)]),e.unref(d)?(e.openBlock(),e.createElementBlock("div",p,[...n[1]||(n[1]=[e.createElementVNode("div",{class:"loading-content"},[e.createElementVNode("div",{class:"spinner"}),e.createElementVNode("p",null,"正在加载文件...")],-1)])])):e.createCommentVNode("",!0)]))}}),[["__scopeId","data-v-
|
|
1
|
+
(function(o,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(o=typeof globalThis<"u"?globalThis:o||self,e(o.OfficePreview={},o.Vue))})(this,(function(o,e){"use strict";const f={class:"file-preview-container"},a={class:"preview-area"},p={key:0,class:"loading-mask"},r=((l,s)=>{const t=l.__vccOpts||l;for(const[d,c]of s)t[d]=c;return t})(e.defineComponent({name:"OfficePreview",__name:"office-preview",props:{fileUrl:{default:""},fileType:{default:""},height:{default:"100%"}},emits:["rendered"],setup(l,{emit:s}){let t=e.ref("pdf"),d=e.ref(!1);const c=l,_=i=>{console.log("fileName",i);const n=i.toLowerCase().split(".").pop();["pdf"].includes(n)?t.value="pdf":["doc","docx"].includes(n)?t.value="doc":["xls","xlsx","csv"].includes(n)?t.value="xls":["ppt","pptx"].includes(n)?(t.value="ppt",console.log("pptxPrviewer",i)):t.value="other"};return e.watch([()=>c.fileType,()=>c.fileUrl],i=>{t.value=i[0],_(i[1])},{immediate:!0}),e.onMounted(async()=>{}),(i,n)=>(e.openBlock(),e.createElementBlock("div",f,[e.createElementVNode("div",a,[n[0]||(n[0]=e.createElementVNode("div",null,"在线预览",-1)),e.createElementVNode("div",null,e.toDisplayString(l.fileUrl),1)]),e.unref(d)?(e.openBlock(),e.createElementBlock("div",p,[...n[1]||(n[1]=[e.createElementVNode("div",{class:"loading-content"},[e.createElementVNode("div",{class:"spinner"}),e.createElementVNode("p",null,"正在加载文件...")],-1)])])):e.createCommentVNode("",!0)]))}}),[["__scopeId","data-v-a3fccd66"]]),m=[r],u={install:l=>{m.forEach(s=>{l.component(s.name,s)})}};o.OfficePreview=r,o.default=u,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|