hookehuyr

feat(office): 添加Office文档预览功能组件

添加OfficeViewer组件支持docx/excel/pptx文件预览
在StudyDetailPage中集成Office文档预览功能
添加相关依赖包@vue-office/docx/excel/pptx和vue-demi
...@@ -29,6 +29,9 @@ ...@@ -29,6 +29,9 @@
29 "@vant/touch-emulator": "^1.4.0", 29 "@vant/touch-emulator": "^1.4.0",
30 "@vant/use": "^1.6.0", 30 "@vant/use": "^1.6.0",
31 "@videojs-player/vue": "^1.0.0", 31 "@videojs-player/vue": "^1.0.0",
32 + "@vue-office/docx": "^1.6.3",
33 + "@vue-office/excel": "^1.7.14",
34 + "@vue-office/pptx": "^1.0.1",
32 "browser-md5-file": "^1.1.1", 35 "browser-md5-file": "^1.1.1",
33 "dayjs": "^1.11.13", 36 "dayjs": "^1.11.13",
34 "lodash": "^4.17.21", 37 "lodash": "^4.17.21",
...@@ -38,6 +41,7 @@ ...@@ -38,6 +41,7 @@
38 "vconsole": "^3.15.1", 41 "vconsole": "^3.15.1",
39 "video.js": "^7.21.7", 42 "video.js": "^7.21.7",
40 "vue": "^3.5.13", 43 "vue": "^3.5.13",
44 + "vue-demi": "0.14.6",
41 "vue-router": "^4.5.0", 45 "vue-router": "^4.5.0",
42 "weixin-js-sdk": "^1.6.5" 46 "weixin-js-sdk": "^1.6.5"
43 }, 47 },
......
...@@ -13,13 +13,16 @@ declare module 'vue' { ...@@ -13,13 +13,16 @@ declare module 'vue' {
13 AudioPlayer: typeof import('./components/ui/AudioPlayer.vue')['default'] 13 AudioPlayer: typeof import('./components/ui/AudioPlayer.vue')['default']
14 BottomNav: typeof import('./components/layout/BottomNav.vue')['default'] 14 BottomNav: typeof import('./components/layout/BottomNav.vue')['default']
15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default'] 15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default']
16 + CollapsibleCalendar: typeof import('./components/ui/CollapsibleCalendar.vue')['default']
16 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default'] 17 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default']
17 CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] 18 CourseCard: typeof import('./components/ui/CourseCard.vue')['default']
18 CourseList: typeof import('./components/courses/CourseList.vue')['default'] 19 CourseList: typeof import('./components/courses/CourseList.vue')['default']
20 + FormPage: typeof import('./components/infoEntry/formPage.vue')['default']
19 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default'] 21 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default']
20 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default'] 22 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default']
21 LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default'] 23 LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default']
22 MenuItem: typeof import('./components/ui/MenuItem.vue')['default'] 24 MenuItem: typeof import('./components/ui/MenuItem.vue')['default']
25 + OfficeViewer: typeof import('./components/ui/OfficeViewer.vue')['default']
23 PdfPreview: typeof import('./components/ui/PdfPreview.vue')['default'] 26 PdfPreview: typeof import('./components/ui/PdfPreview.vue')['default']
24 ReviewPopup: typeof import('./components/courses/ReviewPopup.vue')['default'] 27 ReviewPopup: typeof import('./components/courses/ReviewPopup.vue')['default']
25 RouterLink: typeof import('vue-router')['RouterLink'] 28 RouterLink: typeof import('vue-router')['RouterLink']
......
1 +<template>
2 + <div class="office-viewer">
3 + <!-- 错误状态 -->
4 + <div v-if="error" class="error-container">
5 + <van-icon name="warning-o" size="24" color="#ff6b6b" />
6 + <span class="error-text">{{ error }}</span>
7 + <van-button
8 + type="primary"
9 + size="small"
10 + @click="retry"
11 + class="retry-btn"
12 + >
13 + 重试
14 + </van-button>
15 + </div>
16 +
17 + <!-- 文档预览 -->
18 + <div v-else class="document-container">
19 + <!-- DOCX 文档预览 -->
20 + <vue-office-docx
21 + v-if="fileType === 'docx'"
22 + :src="src"
23 + :style="{ height: containerHeight }"
24 + @rendered="onRendered"
25 + @error="onError"
26 + />
27 +
28 + <!-- Excel 文档预览 -->
29 + <vue-office-excel
30 + v-else-if="fileType === 'excel'"
31 + :src="src"
32 + :style="{ height: containerHeight }"
33 + @rendered="onRendered"
34 + @error="onError"
35 + />
36 +
37 + <!-- PPTX 文档预览 -->
38 + <vue-office-pptx
39 + v-else-if="fileType === 'pptx'"
40 + :src="src"
41 + :style="{ height: containerHeight }"
42 + @rendered="onRendered"
43 + @error="onError"
44 + />
45 +
46 + <!-- 不支持的文件类型 -->
47 + <div v-else class="unsupported-container">
48 + <van-icon name="warning-o" size="24" color="#ff6b6b" />
49 + <span class="unsupported-text">不支持的文件类型: {{ fileType }}</span>
50 + </div>
51 + </div>
52 + </div>
53 +</template>
54 +
55 +<script setup>
56 +import { ref, computed, onMounted, watch } from 'vue'
57 +import VueOfficeDocx from '@vue-office/docx'
58 +import VueOfficeExcel from '@vue-office/excel'
59 +import VueOfficePptx from '@vue-office/pptx'
60 +import '@vue-office/docx/lib/index.css'
61 +import '@vue-office/excel/lib/index.css'
62 +
63 +// Props 定义
64 +const props = defineProps({
65 + // 文件 URL 或 ArrayBuffer 数据
66 + src: {
67 + type: [String, ArrayBuffer],
68 + required: true
69 + },
70 + // 文件类型
71 + fileType: {
72 + type: String,
73 + required: true,
74 + validator: (value) => ['docx', 'excel', 'pptx'].includes(value)
75 + },
76 + // 容器高度
77 + height: {
78 + type: String,
79 + default: '70vh'
80 + }
81 +})
82 +
83 +// Emits 定义
84 +const emit = defineEmits(['rendered', 'error', 'retry'])
85 +
86 +// 响应式数据
87 +const error = ref('')
88 +
89 +// 计算属性
90 +const containerHeight = computed(() => props.height)
91 +
92 +/**
93 + * 文档渲染完成回调
94 + */
95 +const onRendered = () => {
96 + console.log('Office document rendered successfully')
97 + error.value = ''
98 + emit('rendered')
99 +}
100 +
101 +/**
102 + * 文档渲染错误回调
103 + * @param {Error} err - 错误对象
104 + */
105 +const onError = (err) => {
106 + console.error('Office document render error:', err)
107 + error.value = '文档加载失败,请检查文件格式或网络连接'
108 + emit('error', err)
109 +}
110 +
111 +/**
112 + * 重试加载文档
113 + */
114 +const retry = () => {
115 + console.log('Retrying to load office document')
116 + error.value = ''
117 + emit('retry')
118 +}
119 +
120 +/**
121 + * 监听 src 变化,重新加载文档
122 + */
123 +watch(() => props.src, (newSrc) => {
124 + console.log('Office document src changed:', newSrc)
125 + if (newSrc) {
126 + error.value = ''
127 +
128 + // 验证 URL 是否有效
129 + if (typeof newSrc === 'string') {
130 + // 检查是否是有效的 URL
131 + try {
132 + new URL(newSrc)
133 + console.log('Valid URL detected:', newSrc)
134 + } catch (e) {
135 + // 可能是相对路径,检查是否以 http 开头
136 + if (!newSrc.startsWith('http') && !newSrc.startsWith('/')) {
137 + console.warn('Invalid URL format:', newSrc)
138 + error.value = '文档地址格式不正确'
139 + return
140 + }
141 + }
142 + }
143 + }
144 +}, { immediate: true })
145 +
146 +/**
147 + * 监听 fileType 变化
148 + */
149 +watch(() => props.fileType, (newType) => {
150 + console.log('Office document fileType changed:', newType)
151 +})
152 +
153 +// 组件挂载时的初始化
154 +onMounted(() => {
155 + console.log('OfficeViewer 组件已挂载')
156 +})
157 +</script>
158 +
159 +<style lang="less" scoped>
160 +.office-viewer {
161 + width: 100%;
162 + height: 100%;
163 + background: #fff;
164 + border-radius: 8px;
165 + overflow: hidden;
166 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
167 +
168 + .error-container {
169 + display: flex;
170 + flex-direction: column;
171 + align-items: center;
172 + justify-content: center;
173 + height: 200px;
174 + gap: 12px;
175 + padding: 20px;
176 +
177 + .error-text {
178 + font-size: 14px;
179 + color: #666;
180 + text-align: center;
181 + line-height: 1.5;
182 + }
183 +
184 + .retry-btn {
185 + margin-top: 8px;
186 + }
187 + }
188 +
189 + .unsupported-container {
190 + display: flex;
191 + flex-direction: column;
192 + align-items: center;
193 + justify-content: center;
194 + height: 200px;
195 + gap: 12px;
196 + padding: 20px;
197 +
198 + .unsupported-text {
199 + font-size: 14px;
200 + color: #666;
201 + text-align: center;
202 + line-height: 1.5;
203 + }
204 + }
205 +
206 + .document-container {
207 + width: 100%;
208 + height: 100%;
209 + overflow: auto;
210 +
211 + // 覆盖 vue-office 默认样式
212 + :deep(.vue-office-docx),
213 + :deep(.vue-office-excel),
214 + :deep(.vue-office-pptx) {
215 + border: none;
216 + border-radius: 8px;
217 + min-height: 100%;
218 + }
219 +
220 + // Excel 表格样式优化
221 + :deep(.vue-office-excel) {
222 + .luckysheet-cell-main {
223 + font-size: 12px;
224 + }
225 + }
226 +
227 + // DOCX 文档样式优化
228 + :deep(.vue-office-docx) {
229 + .docx-wrapper {
230 + padding: 16px;
231 + min-height: 100%;
232 + background: #fff;
233 + }
234 + }
235 +
236 + // PPTX 演示文稿样式优化
237 + :deep(.vue-office-pptx) {
238 + .pptx-wrapper {
239 + background: #f5f5f5;
240 + min-height: 100%;
241 + }
242 + }
243 + }
244 +}
245 +
246 +// 移动端适配
247 +@media (max-width: 768px) {
248 + .office-viewer {
249 + border-radius: 0;
250 + box-shadow: none;
251 +
252 + .document-container {
253 + // 移动端确保滚动流畅
254 + -webkit-overflow-scrolling: touch;
255 +
256 + :deep(.vue-office-docx) {
257 + .docx-wrapper {
258 + padding: 12px;
259 + font-size: 14px;
260 + line-height: 1.6;
261 + }
262 + }
263 +
264 + :deep(.vue-office-excel) {
265 + .luckysheet-cell-main {
266 + font-size: 11px;
267 + }
268 + }
269 +
270 + :deep(.vue-office-pptx) {
271 + .pptx-wrapper {
272 + padding: 8px;
273 + }
274 + }
275 + }
276 + }
277 +}
278 +</style>
...@@ -122,10 +122,10 @@ const handleMounted = (payload) => { ...@@ -122,10 +122,10 @@ const handleMounted = (payload) => {
122 const errorCode = player.value.error(); 122 const errorCode = player.value.error();
123 if (errorCode) { 123 if (errorCode) {
124 console.error('错误代码:', errorCode.code, '错误信息:', errorCode.message); 124 console.error('错误代码:', errorCode.code, '错误信息:', errorCode.message);
125 - 125 +
126 // 显示用户友好的错误信息 126 // 显示用户友好的错误信息
127 showErrorOverlay.value = true; 127 showErrorOverlay.value = true;
128 - 128 +
129 // 根据错误类型进行处理 129 // 根据错误类型进行处理
130 switch (errorCode.code) { 130 switch (errorCode.code) {
131 case 4: // MEDIA_ERR_SRC_NOT_SUPPORTED 131 case 4: // MEDIA_ERR_SRC_NOT_SUPPORTED
...@@ -231,10 +231,10 @@ const retryLoad = () => { ...@@ -231,10 +231,10 @@ const retryLoad = () => {
231 errorMessage.value = '重试次数已达上限,请稍后再试'; 231 errorMessage.value = '重试次数已达上限,请稍后再试';
232 return; 232 return;
233 } 233 }
234 - 234 +
235 retryCount.value++; 235 retryCount.value++;
236 showErrorOverlay.value = false; 236 showErrorOverlay.value = false;
237 - 237 +
238 if (player.value && !player.value.isDisposed()) { 238 if (player.value && !player.value.isDisposed()) {
239 console.log(`第${retryCount.value}次重试加载视频`); 239 console.log(`第${retryCount.value}次重试加载视频`);
240 player.value.load(); 240 player.value.load();
...@@ -431,7 +431,7 @@ defineExpose({ ...@@ -431,7 +431,7 @@ defineExpose({
431 visibility: visible !important; 431 visibility: visible !important;
432 margin-right: 8px; 432 margin-right: 8px;
433 } 433 }
434 - 434 +
435 :deep(.vjs-playback-rate .vjs-playback-rate-value) { 435 :deep(.vjs-playback-rate .vjs-playback-rate-value) {
436 font-size: 1.3em; 436 font-size: 1.3em;
437 padding: 0 6px; 437 padding: 0 6px;
...@@ -441,14 +441,14 @@ defineExpose({ ...@@ -441,14 +441,14 @@ defineExpose({
441 width: auto; 441 width: auto;
442 margin: 0; 442 margin: 0;
443 } 443 }
444 - 444 +
445 :deep(.vjs-playback-rate .vjs-menu) { 445 :deep(.vjs-playback-rate .vjs-menu) {
446 min-width: 100px; 446 min-width: 100px;
447 max-height: 180px; 447 max-height: 180px;
448 bottom: 120%; 448 bottom: 120%;
449 right: -10px; 449 right: -10px;
450 } 450 }
451 - 451 +
452 :deep(.vjs-playback-rate .vjs-menu-item) { 452 :deep(.vjs-playback-rate .vjs-menu-item) {
453 padding: 12px 16px; 453 padding: 12px 16px;
454 font-size: 16px; 454 font-size: 16px;
...@@ -464,7 +464,7 @@ defineExpose({ ...@@ -464,7 +464,7 @@ defineExpose({
464 :deep(.vjs-playback-rate) { 464 :deep(.vjs-playback-rate) {
465 margin-right: 6px; 465 margin-right: 6px;
466 } 466 }
467 - 467 +
468 :deep(.vjs-playback-rate .vjs-playback-rate-value) { 468 :deep(.vjs-playback-rate .vjs-playback-rate-value) {
469 font-size: 1.2em; 469 font-size: 1.2em;
470 padding: 0 4px; 470 padding: 0 4px;
...@@ -474,12 +474,12 @@ defineExpose({ ...@@ -474,12 +474,12 @@ defineExpose({
474 width: auto; 474 width: auto;
475 margin: 0; 475 margin: 0;
476 } 476 }
477 - 477 +
478 :deep(.vjs-playback-rate .vjs-menu) { 478 :deep(.vjs-playback-rate .vjs-menu) {
479 min-width: 90px; 479 min-width: 90px;
480 right: -5px; 480 right: -5px;
481 } 481 }
482 - 482 +
483 :deep(.vjs-playback-rate .vjs-menu-item) { 483 :deep(.vjs-playback-rate .vjs-menu-item) {
484 padding: 10px 12px; 484 padding: 10px 12px;
485 font-size: 15px; 485 font-size: 15px;
......
...@@ -265,6 +265,32 @@ ...@@ -265,6 +265,32 @@
265 <!-- PDF预览 --> 265 <!-- PDF预览 -->
266 <PdfPreview v-model:show="pdfShow" :url="pdfUrl" :title="pdfTitle" @onLoad="onPdfLoad" /> 266 <PdfPreview v-model:show="pdfShow" :url="pdfUrl" :title="pdfTitle" @onLoad="onPdfLoad" />
267 267
268 + <!-- Office 文档预览弹窗 -->
269 + <van-popup
270 + v-model:show="officeShow"
271 + position="center"
272 + round
273 + closeable
274 + :style="{ height: '80%', width: '90%' }"
275 + >
276 + <div class="h-full flex flex-col">
277 + <div class="p-4 border-b border-gray-200">
278 + <h3 class="text-lg font-medium text-center truncate">{{ officeTitle }}</h3>
279 + </div>
280 + <div class="flex-1 overflow-auto">
281 + <OfficeViewer
282 + v-if="officeShow && officeUrl && officeFileType"
283 + :src="officeUrl"
284 + :file-type="officeFileType"
285 + height="100%"
286 + @rendered="onOfficeRendered"
287 + @error="onOfficeError"
288 + @retry="onOfficeRetry"
289 + />
290 + </div>
291 + </div>
292 + </van-popup>
293 +
268 <!-- 音频播放器弹窗 --> 294 <!-- 音频播放器弹窗 -->
269 <van-popup 295 <van-popup
270 v-model:show="audioShow" 296 v-model:show="audioShow"
...@@ -542,9 +568,18 @@ ...@@ -542,9 +568,18 @@
542 568
543 <!-- 移动端:根据文件类型显示不同的预览按钮 --> 569 <!-- 移动端:根据文件类型显示不同的预览按钮 -->
544 <template v-else> 570 <template v-else>
571 + <!-- Office 文档显示预览按钮 -->
572 + <button
573 + v-if="isOfficeFile(file.url)"
574 + @click="showOfficeDocument(file)"
575 + class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2"
576 + >
577 + <van-icon name="description" size="16" />
578 + 文档预览
579 + </button>
545 <!-- PDF文件显示在线查看按钮 --> 580 <!-- PDF文件显示在线查看按钮 -->
546 <button 581 <button
547 - v-if="file.url && file.url.toLowerCase().includes('.pdf')" 582 + v-else-if="file.url && file.url.toLowerCase().includes('.pdf')"
548 @click="showPdf(file)" 583 @click="showPdf(file)"
549 class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2" 584 class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2"
550 > 585 >
...@@ -613,6 +648,7 @@ import { useTitle } from '@vueuse/core'; ...@@ -613,6 +648,7 @@ import { useTitle } from '@vueuse/core';
613 import VideoPlayer from '@/components/ui/VideoPlayer.vue'; 648 import VideoPlayer from '@/components/ui/VideoPlayer.vue';
614 import AudioPlayer from '@/components/ui/AudioPlayer.vue'; 649 import AudioPlayer from '@/components/ui/AudioPlayer.vue';
615 import FrostedGlass from '@/components/ui/FrostedGlass.vue'; 650 import FrostedGlass from '@/components/ui/FrostedGlass.vue';
651 +import OfficeViewer from '@/components/ui/OfficeViewer.vue';
616 import dayjs from 'dayjs'; 652 import dayjs from 'dayjs';
617 import { formatDate, wxInfo } from '@/utils/tools' 653 import { formatDate, wxInfo } from '@/utils/tools'
618 import axios from 'axios'; 654 import axios from 'axios';
...@@ -817,6 +853,12 @@ const pdfShow = ref(false); ...@@ -817,6 +853,12 @@ const pdfShow = ref(false);
817 const pdfTitle = ref(''); 853 const pdfTitle = ref('');
818 const pdfUrl = ref(''); 854 const pdfUrl = ref('');
819 855
856 +// Office 文档预览相关
857 +const officeShow = ref(false);
858 +const officeTitle = ref('');
859 +const officeUrl = ref('');
860 +const officeFileType = ref('');
861 +
820 // 音频播放器相关 862 // 音频播放器相关
821 const audioShow = ref(false); 863 const audioShow = ref(false);
822 const audioTitle = ref(''); 864 const audioTitle = ref('');
...@@ -842,6 +884,46 @@ const showPdf = ({ title, url, meta_id }) => { ...@@ -842,6 +884,46 @@ const showPdf = ({ title, url, meta_id }) => {
842 }; 884 };
843 885
844 /** 886 /**
887 + * 显示 Office 文档预览
888 + * @param {Object} file - 文件对象,包含title、url、meta_id
889 + */
890 +const showOfficeDocument = ({ title, url, meta_id }) => {
891 + console.log('showOfficeDocument called with:', { title, url, meta_id });
892 +
893 + // 清理 URL 中的反引号和多余空格
894 + const cleanUrl = url.replace(/`/g, '').trim();
895 +
896 + officeTitle.value = title;
897 + officeUrl.value = cleanUrl;
898 + officeFileType.value = getOfficeFileType(cleanUrl);
899 +
900 + console.log('Office document props set:', {
901 + title: officeTitle.value,
902 + url: officeUrl.value,
903 + fileType: officeFileType.value
904 + });
905 +
906 + // 验证 URL 格式
907 + try {
908 + new URL(cleanUrl);
909 + console.log('URL validation passed:', cleanUrl);
910 + } catch (error) {
911 + console.error('Invalid URL format:', cleanUrl, error);
912 + showToast('文档链接格式不正确');
913 + return;
914 + }
915 +
916 + officeShow.value = true;
917 +
918 + // 新增记录
919 + let paramsObj = {
920 + schedule_id: courseId.value,
921 + meta_id
922 + }
923 + addRecord(paramsObj);
924 +};
925 +
926 +/**
845 * 显示音频播放器 927 * 显示音频播放器
846 * @param {Object} file - 文件对象,包含title、url、meta_id 928 * @param {Object} file - 文件对象,包含title、url、meta_id
847 */ 929 */
...@@ -901,6 +983,31 @@ const onPdfLoad = (load) => { ...@@ -901,6 +983,31 @@ const onPdfLoad = (load) => {
901 // console.warn('pdf加载状态', load); 983 // console.warn('pdf加载状态', load);
902 }; 984 };
903 985
986 +/**
987 + * Office 文档渲染完成回调
988 + */
989 +const onOfficeRendered = () => {
990 + console.log('Office 文档渲染完成');
991 + showToast('文档加载完成');
992 +};
993 +
994 +/**
995 + * Office 文档渲染错误回调
996 + * @param {Error} error - 错误对象
997 + */
998 +const onOfficeError = (error) => {
999 + console.error('Office 文档渲染失败:', error);
1000 + showToast('文档加载失败,请重试');
1001 +};
1002 +
1003 +/**
1004 + * Office 文档重试回调
1005 + */
1006 +const onOfficeRetry = () => {
1007 + console.log('重试加载 Office 文档');
1008 + // 可以在这里添加重新获取文档的逻辑
1009 +};
1010 +
904 const courseId = computed(() => { 1011 const courseId = computed(() => {
905 return route.params.id || ''; 1012 return route.params.id || '';
906 }); 1013 });
...@@ -1396,6 +1503,44 @@ const isImageFile = (fileName) => { ...@@ -1396,6 +1503,44 @@ const isImageFile = (fileName) => {
1396 } 1503 }
1397 1504
1398 /** 1505 /**
1506 + * 判断文件是否为 Office 文档
1507 + * @param {string} fileName - 文件名
1508 + * @returns {boolean} 是否为 Office 文档
1509 + */
1510 +const isOfficeFile = (fileName) => {
1511 + if (!fileName || typeof fileName !== 'string') {
1512 + return false;
1513 + }
1514 +
1515 + const extension = fileName.split('.').pop().toLowerCase();
1516 + const officeTypes = ['docx', 'xlsx', 'xls', 'pptx'];
1517 + return officeTypes.includes(extension);
1518 +}
1519 +
1520 +/**
1521 + * 获取 Office 文档类型
1522 + * @param {string} fileName - 文件名
1523 + * @returns {string} 文档类型 (docx, excel, pptx)
1524 + */
1525 +const getOfficeFileType = (fileName) => {
1526 + if (!fileName || typeof fileName !== 'string') {
1527 + return '';
1528 + }
1529 +
1530 + const extension = fileName.split('.').pop().toLowerCase();
1531 +
1532 + if (extension === 'docx') {
1533 + return 'docx';
1534 + } else if (extension === 'xlsx' || extension === 'xls') {
1535 + return 'excel';
1536 + } else if (extension === 'pptx') {
1537 + return 'pptx';
1538 + }
1539 +
1540 + return '';
1541 +}
1542 +
1543 +/**
1399 * 音频播放事件 1544 * 音频播放事件
1400 * @param audio 音频对象 1545 * @param audio 音频对象
1401 */ 1546 */
......
...@@ -713,6 +713,21 @@ ...@@ -713,6 +713,21 @@
713 resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz#71a8fc82d4d2e425af304c35bf389506f674d89b" 713 resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz#71a8fc82d4d2e425af304c35bf389506f674d89b"
714 integrity sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg== 714 integrity sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==
715 715
716 +"@vue-office/docx@^1.6.3":
717 + version "1.6.3"
718 + resolved "https://registry.yarnpkg.com/@vue-office/docx/-/docx-1.6.3.tgz#0c183b13553c029fea702eb8b8b4260a7e1f0961"
719 + integrity sha512-Cs+3CAaRBOWOiW4XAhTwwxJ0dy8cPIf6DqfNvYcD3YACiLwO4kuawLF2IAXxyijhbuOeoFsfvoVbOc16A/4bZA==
720 +
721 +"@vue-office/excel@^1.7.14":
722 + version "1.7.14"
723 + resolved "https://registry.yarnpkg.com/@vue-office/excel/-/excel-1.7.14.tgz#6e1446abae9690a09eed6f5e8c09bb923b3d5b10"
724 + integrity sha512-pVUgt+emDQUnW7q22CfnQ+jl43mM/7IFwYzOg7lwOwPEbiVB4K4qEQf+y/bc4xGXz75w1/e3Kz3G6wAafmFBFg==
725 +
726 +"@vue-office/pptx@^1.0.1":
727 + version "1.0.1"
728 + resolved "https://registry.yarnpkg.com/@vue-office/pptx/-/pptx-1.0.1.tgz#f34d5a7aa78cd534c5724540dbe53f0539e94b3e"
729 + integrity sha512-+V7Kctzl6f6+Yk4NaD/wQGRIkqLWcowe0jEhPexWQb8Oilbzt1OyhWRWcMsxNDTdrgm6aMLP+0/tmw27cxddMg==
730 +
716 "@vue/babel-helper-vue-transform-on@1.4.0": 731 "@vue/babel-helper-vue-transform-on@1.4.0":
717 version "1.4.0" 732 version "1.4.0"
718 resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz#616020488692a9c42a613280d62ed1b727045d95" 733 resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz#616020488692a9c42a613280d62ed1b727045d95"
...@@ -2855,6 +2870,11 @@ vite@^6.2.0: ...@@ -2855,6 +2870,11 @@ vite@^6.2.0:
2855 optionalDependencies: 2870 optionalDependencies:
2856 fsevents "~2.3.3" 2871 fsevents "~2.3.3"
2857 2872
2873 +vue-demi@0.14.6:
2874 + version "0.14.6"
2875 + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92"
2876 + integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
2877 +
2858 vue-router@^4.5.0: 2878 vue-router@^4.5.0:
2859 version "4.5.1" 2879 version "4.5.1"
2860 resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.1.tgz#47bffe2d3a5479d2886a9a244547a853aa0abf69" 2880 resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.1.tgz#47bffe2d3a5479d2886a9a244547a853aa0abf69"
......