|
@@ -0,0 +1,533 @@
|
|
|
+<template>
|
|
|
+ <div class="document-preview-page">
|
|
|
+ <div class="preview-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <button class="back-btn" @click="goBack">
|
|
|
+ <LeftOutlined />
|
|
|
+ 返回
|
|
|
+ </button>
|
|
|
+ <div class="document-info">
|
|
|
+ <h2>{{ documentTitle }}</h2>
|
|
|
+ <span class="document-url">{{ documentUrl }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <button class="action-btn" @click="downloadDocument" title="下载文档">
|
|
|
+ <DownloadOutlined />
|
|
|
+ 下载
|
|
|
+ </button>
|
|
|
+ <button class="action-btn" @click="refreshPreview" title="刷新预览">
|
|
|
+ <ReloadOutlined />
|
|
|
+ 刷新
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="preview-container">
|
|
|
+ <div v-if="loading" class="loading-container">
|
|
|
+ <a-spin size="large">
|
|
|
+ <template #indicator>
|
|
|
+ <LoadingOutlined style="font-size: 24px" spin />
|
|
|
+ </template>
|
|
|
+ </a-spin>
|
|
|
+ <p>正在加载文档预览...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="error" class="error-container">
|
|
|
+ <ExclamationCircleOutlined class="error-icon" />
|
|
|
+ <h3>预览失败</h3>
|
|
|
+ <p>{{ error }}</p>
|
|
|
+ <div class="error-actions">
|
|
|
+ <a-button type="primary" @click="refreshPreview">重试</a-button>
|
|
|
+ <a-button @click="downloadDocument">下载文档</a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="preview-content">
|
|
|
+ <!-- Word文档预览 -->
|
|
|
+ <div v-if="isWordFile" class="word-preview">
|
|
|
+ <vue-office-docx
|
|
|
+ :src="documentUrl"
|
|
|
+ style="height: 100%;"
|
|
|
+ @rendered="handleRendered"
|
|
|
+ @error="handleError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Excel文档预览 -->
|
|
|
+ <div v-else-if="isExcelFile" class="excel-preview">
|
|
|
+ <vue-office-excel
|
|
|
+ :src="documentUrl"
|
|
|
+ :options="excelOptions"
|
|
|
+ style="height: 100%;"
|
|
|
+ @rendered="handleRendered"
|
|
|
+ @error="handleError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- PDF预览 -->
|
|
|
+ <div v-else-if="isPdfFile" class="pdf-preview">
|
|
|
+ <iframe
|
|
|
+ :src="documentUrl"
|
|
|
+ style="width: 100%; height: 100%; border: none;"
|
|
|
+ frameborder="0"
|
|
|
+ @load="handleIframeLoad"
|
|
|
+ @error="handleIframeError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图片预览 -->
|
|
|
+ <div v-else-if="isImageFile" class="image-preview">
|
|
|
+ <img
|
|
|
+ :src="documentUrl"
|
|
|
+ class="preview-image"
|
|
|
+ alt="文档预览"
|
|
|
+ @load="handleImageLoad"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频预览 -->
|
|
|
+ <div v-else-if="isVideoFile" class="video-preview">
|
|
|
+ <video
|
|
|
+ :src="documentUrl"
|
|
|
+ controls
|
|
|
+ class="preview-video"
|
|
|
+ @loadeddata="handleVideoLoad"
|
|
|
+ @error="handleVideoError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 其他文件类型 -->
|
|
|
+ <div v-else class="unsupported-preview">
|
|
|
+ <FileOutlined class="file-icon" />
|
|
|
+ <h3>不支持预览</h3>
|
|
|
+ <p>该文件类型暂不支持在线预览,请下载后查看</p>
|
|
|
+ <a-button type="primary" @click="downloadDocument">下载文档</a-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { defineComponent, ref, computed, onMounted } from 'vue';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import {
|
|
|
+ LeftOutlined,
|
|
|
+ DownloadOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ LoadingOutlined,
|
|
|
+ ExclamationCircleOutlined,
|
|
|
+ FileOutlined
|
|
|
+} from '@ant-design/icons-vue';
|
|
|
+import { message } from 'ant-design-vue';
|
|
|
+import VueOfficeDocx from '@vue-office/docx';
|
|
|
+import VueOfficeExcel from '@vue-office/excel';
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: 'DocumentPreviewPage',
|
|
|
+ components: {
|
|
|
+ LeftOutlined,
|
|
|
+ DownloadOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ LoadingOutlined,
|
|
|
+ ExclamationCircleOutlined,
|
|
|
+ FileOutlined,
|
|
|
+ VueOfficeDocx,
|
|
|
+ VueOfficeExcel
|
|
|
+ },
|
|
|
+ setup() {
|
|
|
+ const route = useRoute();
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ const loading = ref(true);
|
|
|
+ const error = ref('');
|
|
|
+ const documentUrl = ref('');
|
|
|
+ const documentTitle = ref('');
|
|
|
+
|
|
|
+ // Excel预览配置
|
|
|
+ const excelOptions = ref({
|
|
|
+ xls: false,
|
|
|
+ minColLength: 0,
|
|
|
+ minRowLength: 0,
|
|
|
+ widthOffset: 10,
|
|
|
+ heightOffset: 10,
|
|
|
+ forceRender: true,
|
|
|
+ maxSheetLength: 10
|
|
|
+ });
|
|
|
+
|
|
|
+ // 文件类型判断
|
|
|
+ const isWordFile = computed(() => {
|
|
|
+ return documentUrl.value.toLowerCase().includes('.docx') ||
|
|
|
+ documentUrl.value.toLowerCase().includes('.doc');
|
|
|
+ });
|
|
|
+
|
|
|
+ const isExcelFile = computed(() => {
|
|
|
+ return documentUrl.value.toLowerCase().includes('.xlsx') ||
|
|
|
+ documentUrl.value.toLowerCase().includes('.xls');
|
|
|
+ });
|
|
|
+
|
|
|
+ const isPdfFile = computed(() => {
|
|
|
+ return documentUrl.value.toLowerCase().includes('.pdf');
|
|
|
+ });
|
|
|
+
|
|
|
+ const isImageFile = computed(() => {
|
|
|
+ const imageTypes = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
|
|
|
+ return imageTypes.some(type => documentUrl.value.toLowerCase().includes(type));
|
|
|
+ });
|
|
|
+
|
|
|
+ const isVideoFile = computed(() => {
|
|
|
+ const videoTypes = ['.mp4', '.webm', '.ogg', '.avi', '.mov'];
|
|
|
+ return videoTypes.some(type => documentUrl.value.toLowerCase().includes(type));
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化
|
|
|
+ onMounted(() => {
|
|
|
+ initDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ const initDocument = () => {
|
|
|
+ const url = route.query.url;
|
|
|
+ const title = route.query.title || '文档预览';
|
|
|
+
|
|
|
+ if (!url) {
|
|
|
+ error.value = '缺少文档链接参数';
|
|
|
+ loading.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ documentUrl.value = decodeURIComponent(url);
|
|
|
+ documentTitle.value = decodeURIComponent(title);
|
|
|
+
|
|
|
+ // 模拟加载时间
|
|
|
+ setTimeout(() => {
|
|
|
+ loading.value = false;
|
|
|
+ }, 1000);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 事件处理
|
|
|
+ const handleRendered = () => {
|
|
|
+ console.log('文档渲染完成');
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleError = (err) => {
|
|
|
+ console.error('文档渲染失败:', err);
|
|
|
+ error.value = '文档渲染失败,请检查文档格式或网络连接';
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleIframeLoad = () => {
|
|
|
+ console.log('iframe加载完成');
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleIframeError = () => {
|
|
|
+ error.value = '文档加载失败,请检查文档链接是否有效';
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleImageLoad = () => {
|
|
|
+ console.log('图片加载完成');
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleImageError = () => {
|
|
|
+ error.value = '图片加载失败,请检查图片链接是否有效';
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleVideoLoad = () => {
|
|
|
+ console.log('视频加载完成');
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleVideoError = () => {
|
|
|
+ error.value = '视频加载失败,请检查视频链接是否有效';
|
|
|
+ loading.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 操作函数
|
|
|
+ const goBack = () => {
|
|
|
+ router.go(-1);
|
|
|
+ };
|
|
|
+
|
|
|
+ const downloadDocument = () => {
|
|
|
+ if (documentUrl.value) {
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = documentUrl.value;
|
|
|
+ link.download = documentTitle.value;
|
|
|
+ link.target = '_blank';
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ message.success('开始下载文档');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const refreshPreview = () => {
|
|
|
+ loading.value = true;
|
|
|
+ error.value = '';
|
|
|
+ // 重新加载页面
|
|
|
+ window.location.reload();
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ loading,
|
|
|
+ error,
|
|
|
+ documentUrl,
|
|
|
+ documentTitle,
|
|
|
+ excelOptions,
|
|
|
+ isWordFile,
|
|
|
+ isExcelFile,
|
|
|
+ isPdfFile,
|
|
|
+ isImageFile,
|
|
|
+ isVideoFile,
|
|
|
+ handleRendered,
|
|
|
+ handleError,
|
|
|
+ handleIframeLoad,
|
|
|
+ handleIframeError,
|
|
|
+ handleImageLoad,
|
|
|
+ handleImageError,
|
|
|
+ handleVideoLoad,
|
|
|
+ handleVideoError,
|
|
|
+ goBack,
|
|
|
+ downloadDocument,
|
|
|
+ refreshPreview
|
|
|
+ };
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.document-preview-page {
|
|
|
+ height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-header {
|
|
|
+ background: white;
|
|
|
+ padding: 16px 24px;
|
|
|
+ border-bottom: 1px solid #e8e8e8;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.back-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ background: white;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #1677ff;
|
|
|
+ color: #1677ff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.document-info {
|
|
|
+ h2 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #262626;
|
|
|
+ max-width: 400px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .document-url {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8c8c8c;
|
|
|
+ max-width: 500px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ display: block;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.header-right {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.action-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ background: white;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #1677ff;
|
|
|
+ color: #1677ff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-container {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ color: #8c8c8c;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.error-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ gap: 16px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .error-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ color: #ff4d4f;
|
|
|
+ }
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ margin: 0;
|
|
|
+ color: #262626;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ color: #8c8c8c;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content {
|
|
|
+ height: 100%;
|
|
|
+ overflow: auto;
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+.word-preview,
|
|
|
+.excel-preview,
|
|
|
+.pdf-preview,
|
|
|
+.image-preview,
|
|
|
+.video-preview {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+ display: block;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-video {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.unsupported-preview {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ gap: 16px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .file-icon {
|
|
|
+ font-size: 64px;
|
|
|
+ color: #8c8c8c;
|
|
|
+ }
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ margin: 0;
|
|
|
+ color: #262626;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ color: #8c8c8c;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式设计
|
|
|
+@media screen and (max-width: 768px) {
|
|
|
+ .preview-header {
|
|
|
+ padding: 12px 16px;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-left {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-right {
|
|
|
+ width: 100%;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .document-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ h2 {
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .document-url {
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|