2025-11-04 13:22:28 +08:00
|
|
|
|
<!-- OnlyOfficeViewer.vue -->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="onlyoffice-viewer" :style="{ height: height }">
|
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
|
<div v-if="!loading && !error" class="viewer-toolbar">
|
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
|
<button @click="handleClose" class="toolbar-btn" :disabled="closing">
|
|
|
|
|
|
<i class="el-icon-back"></i> 返回
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span class="document-title" :title="documentName">{{ documentName }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
|
<button v-if="allowDownload" @click="downloadDocument" class="toolbar-btn download" :disabled="downloading">
|
|
|
|
|
|
<i class="el-icon-download"></i> {{ downloading ? '下载中...' : '下载' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button v-if="!isMobile && allowFullscreen" @click="toggleFullscreen" class="toolbar-btn">
|
|
|
|
|
|
<i :class="isFullscreen ? 'el-icon-copy-document' : 'el-icon-full-screen'"></i>
|
|
|
|
|
|
{{ isFullscreen ? '退出全屏' : '全屏' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
|
<div v-if="loading" class="viewer-loading">
|
|
|
|
|
|
<div class="loading-content">
|
|
|
|
|
|
<div class="loading-spinner"></div>
|
|
|
|
|
|
<p>{{ loadingText }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 错误状态 -->
|
|
|
|
|
|
<div v-if="error" class="viewer-error">
|
|
|
|
|
|
<div class="error-content">
|
|
|
|
|
|
<div class="error-icon">❌</div>
|
|
|
|
|
|
<h3>加载失败</h3>
|
|
|
|
|
|
<p>{{ errorMessage }}</p>
|
|
|
|
|
|
<button @click="retry" class="retry-btn" :disabled="loading">重试</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 编辑器容器 -->
|
2025-11-05 09:47:20 +08:00
|
|
|
|
<div v-if="!loading && !error" ref="editorContainer" class="editor-container"
|
|
|
|
|
|
:class="{ 'fullscreen': isFullscreen }" :style="{ height: containerHeight, width: '100%' }"></div>
|
2025-11-04 13:22:28 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 移动端提示 -->
|
|
|
|
|
|
<div v-if="isMobile && !loading && !error" class="mobile-tip">
|
|
|
|
|
|
<p>💡 在移动端建议横屏查看以获得更好体验</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { getConfigAPI } from '@/api/common/onlyOfficeViewer';
|
|
|
|
|
|
import { downloadFileWithLoading } from '@/utils/download';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'OnlyOfficeViewer',
|
|
|
|
|
|
|
|
|
|
|
|
props: {
|
|
|
|
|
|
// 文档ID
|
|
|
|
|
|
documentId: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
},
|
|
|
|
|
|
// 文档名称
|
|
|
|
|
|
documentName: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
},
|
|
|
|
|
|
// 组件高度
|
|
|
|
|
|
height: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: '600px'
|
|
|
|
|
|
},
|
|
|
|
|
|
// 是否允许下载
|
|
|
|
|
|
allowDownload: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: true
|
|
|
|
|
|
},
|
|
|
|
|
|
// 是否允许全屏
|
|
|
|
|
|
allowFullscreen: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: true
|
|
|
|
|
|
},
|
|
|
|
|
|
// 查看模式:view(查看)或 edit(编辑)
|
|
|
|
|
|
mode: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: 'view',
|
|
|
|
|
|
validator: (value) => ['view', 'edit'].includes(value)
|
|
|
|
|
|
},
|
|
|
|
|
|
// 下载API路径(可选,默认使用统一的下载接口)
|
|
|
|
|
|
downloadUrl: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
loadingText: '正在加载文档...',
|
|
|
|
|
|
error: false,
|
|
|
|
|
|
errorMessage: '',
|
|
|
|
|
|
editor: null,
|
|
|
|
|
|
isFullscreen: false,
|
|
|
|
|
|
isMobile: false,
|
|
|
|
|
|
downloading: false,
|
|
|
|
|
|
closing: false
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
// API基础URL
|
|
|
|
|
|
apiBaseUrl() {
|
|
|
|
|
|
return process.env.VUE_APP_BASE_API || '';
|
|
|
|
|
|
},
|
|
|
|
|
|
// OnlyOffice服务URL
|
|
|
|
|
|
onlyOfficeUrl() {
|
|
|
|
|
|
return process.env.VUE_APP_ONLYOFFICE_URL || process.env.VUE_APP_BASE_API || '';
|
|
|
|
|
|
},
|
|
|
|
|
|
// 容器高度(减去工具栏高度)
|
|
|
|
|
|
containerHeight() {
|
|
|
|
|
|
if (this.isFullscreen) {
|
|
|
|
|
|
return '100vh';
|
|
|
|
|
|
}
|
|
|
|
|
|
// 工具栏高度约48px,减去后计算容器高度
|
2025-11-05 09:47:20 +08:00
|
|
|
|
if (this.height.includes('vh')) {
|
|
|
|
|
|
// 如果是vh单位,直接计算
|
|
|
|
|
|
const vhValue = parseFloat(this.height);
|
|
|
|
|
|
return `calc(${vhValue}vh - 48px)`;
|
|
|
|
|
|
}
|
2025-11-04 13:22:28 +08:00
|
|
|
|
const heightValue = parseInt(this.height);
|
|
|
|
|
|
if (isNaN(heightValue)) {
|
|
|
|
|
|
return 'calc(100% - 48px)';
|
|
|
|
|
|
}
|
2025-11-05 09:47:20 +08:00
|
|
|
|
return `${Math.max(heightValue - 48, 200)}px`; // 最小高度200px
|
2025-11-04 13:22:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.checkMobile();
|
|
|
|
|
|
this.setupFullscreenListener();
|
|
|
|
|
|
this.initViewer();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
|
this.cleanup();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化查看器
|
|
|
|
|
|
*/
|
|
|
|
|
|
async initViewer() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.loading = true;
|
|
|
|
|
|
this.loadingText = '正在加载文档...';
|
|
|
|
|
|
this.error = false;
|
|
|
|
|
|
this.errorMessage = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 检查容器是否存在
|
|
|
|
|
|
if (!this.$refs.editorContainer) {
|
|
|
|
|
|
throw new Error('编辑器容器未找到');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载 OnlyOffice 脚本
|
|
|
|
|
|
this.loadingText = '正在加载 OnlyOffice 脚本...';
|
|
|
|
|
|
await this.loadOnlyOfficeScript();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取编辑器配置
|
|
|
|
|
|
this.loadingText = '正在获取文档配置...';
|
|
|
|
|
|
const config = await this.getEditorConfig();
|
2025-11-05 09:47:20 +08:00
|
|
|
|
console.log('OnlyOffice 配置:', config);
|
|
|
|
|
|
|
|
|
|
|
|
// 验证配置格式
|
|
|
|
|
|
if (!config || typeof config !== 'object') {
|
|
|
|
|
|
throw new Error('获取的配置格式不正确');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证必要的配置项
|
|
|
|
|
|
if (!config.document || !config.document.url) {
|
|
|
|
|
|
throw new Error('配置中缺少文档URL');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!config.documentType) {
|
|
|
|
|
|
console.warn('配置中缺少 documentType,将尝试自动检测');
|
|
|
|
|
|
}
|
2025-11-04 13:22:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建编辑器实例
|
|
|
|
|
|
if (!window.DocsAPI) {
|
|
|
|
|
|
throw new Error('OnlyOffice API 未加载成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.loadingText = '正在初始化编辑器...';
|
|
|
|
|
|
|
2025-11-05 09:47:20 +08:00
|
|
|
|
// 确保容器有正确的尺寸
|
|
|
|
|
|
const container = this.$refs.editorContainer;
|
|
|
|
|
|
if (container) {
|
|
|
|
|
|
// 确保容器可见
|
|
|
|
|
|
container.style.display = 'block';
|
|
|
|
|
|
container.style.width = '100%';
|
|
|
|
|
|
|
|
|
|
|
|
// 等待下一个tick确保DOM已更新
|
|
|
|
|
|
await this.$nextTick();
|
|
|
|
|
|
|
|
|
|
|
|
// 验证容器尺寸
|
|
|
|
|
|
const rect = container.getBoundingClientRect();
|
|
|
|
|
|
if (rect.width === 0 || rect.height === 0) {
|
|
|
|
|
|
console.warn('容器尺寸为0,等待尺寸调整...', rect);
|
|
|
|
|
|
// 等待一段时间再初始化
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建编辑器实例
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.editor = new window.DocsAPI.DocEditor(
|
|
|
|
|
|
this.$refs.editorContainer,
|
|
|
|
|
|
config
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听编辑器事件(如果存在)
|
|
|
|
|
|
if (this.editor && this.editor.events) {
|
|
|
|
|
|
// 等待编辑器准备就绪
|
|
|
|
|
|
this.editor.events.on('onDocumentReady', () => {
|
|
|
|
|
|
console.log('OnlyOffice 文档已准备就绪');
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.editor.events.on('onAppReady', () => {
|
|
|
|
|
|
console.log('OnlyOffice 应用已准备就绪');
|
|
|
|
|
|
// 应用准备就绪后,可以隐藏加载状态
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (this.loading) {
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.editor.events.on('onError', (error) => {
|
|
|
|
|
|
console.error('OnlyOffice 错误:', error);
|
|
|
|
|
|
this.handleError(`编辑器错误: ${error?.data || error?.message || '未知错误'}`);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置超时,防止一直加载
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (this.loading) {
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
console.warn('编辑器初始化超时,但可能仍在加载中');
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 30000); // 30秒超时
|
|
|
|
|
|
|
|
|
|
|
|
// 默认设置加载完成
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
this.$emit('loaded', this.editor);
|
|
|
|
|
|
} catch (editorError) {
|
|
|
|
|
|
console.error('创建编辑器实例失败:', editorError);
|
|
|
|
|
|
throw new Error(`创建编辑器失败: ${editorError.message}`);
|
|
|
|
|
|
}
|
2025-11-04 13:22:28 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('初始化文档查看器失败:', error);
|
|
|
|
|
|
const errorMsg = error?.message || error?.response?.data?.msg || '加载文档失败,请重试';
|
|
|
|
|
|
this.handleError(errorMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载 OnlyOffice 脚本
|
|
|
|
|
|
* @returns {Promise}
|
|
|
|
|
|
*/
|
|
|
|
|
|
loadOnlyOfficeScript() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 如果已经加载,直接返回
|
|
|
|
|
|
if (window.DocsAPI) {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否正在加载
|
|
|
|
|
|
const existingScript = document.querySelector('script[data-onlyoffice-api]');
|
|
|
|
|
|
if (existingScript) {
|
|
|
|
|
|
existingScript.addEventListener('load', resolve);
|
|
|
|
|
|
existingScript.addEventListener('error', () => {
|
|
|
|
|
|
reject(new Error('加载 OnlyOffice 脚本失败'));
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并加载脚本
|
|
|
|
|
|
const script = document.createElement('script');
|
|
|
|
|
|
script.src = `${this.onlyOfficeUrl}/web-apps/apps/api/documents/api.js`;
|
|
|
|
|
|
script.setAttribute('data-onlyoffice-api', 'true');
|
|
|
|
|
|
|
|
|
|
|
|
script.onload = () => {
|
|
|
|
|
|
// 等待一小段时间确保 API 完全初始化
|
|
|
|
|
|
setTimeout(resolve, 100);
|
|
|
|
|
|
};
|
|
|
|
|
|
script.onerror = () => {
|
|
|
|
|
|
reject(new Error('加载 OnlyOffice 脚本失败,请检查 OnlyOffice 服务是否正常运行'));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
document.head.appendChild(script);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取编辑器配置
|
|
|
|
|
|
* @returns {Promise<Object>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getEditorConfig() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getConfigAPI({
|
|
|
|
|
|
fileId: this.documentId,
|
|
|
|
|
|
fileName: this.documentName,
|
|
|
|
|
|
mode: this.mode
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res.code !== 200) {
|
|
|
|
|
|
throw new Error(res.msg || '获取编辑器配置失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return res.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取编辑器配置失败:', error);
|
|
|
|
|
|
const errorMsg = error?.response?.data?.msg || error?.message || '获取文档配置失败';
|
|
|
|
|
|
throw new Error(errorMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文档
|
|
|
|
|
|
*/
|
|
|
|
|
|
async downloadDocument() {
|
|
|
|
|
|
if (!this.allowDownload || this.downloading) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.downloading = true;
|
|
|
|
|
|
this.$emit('downloading');
|
|
|
|
|
|
|
|
|
|
|
|
// 使用统一的下载方法
|
|
|
|
|
|
let downloadPath = this.downloadUrl;
|
|
|
|
|
|
if (!downloadPath) {
|
|
|
|
|
|
// 默认下载路径
|
|
|
|
|
|
downloadPath = `smartBid/documents/download?fileId=${this.documentId}&fileName=${encodeURIComponent(this.documentName)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await downloadFileWithLoading(
|
|
|
|
|
|
downloadPath,
|
|
|
|
|
|
this.documentName,
|
|
|
|
|
|
'正在下载文档,请稍候...'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.$emit('download-success');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('下载文档失败:', error);
|
|
|
|
|
|
this.$message?.error?.('下载文档失败,请稍后重试');
|
|
|
|
|
|
this.$emit('download-error', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.downloading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 切换全屏
|
|
|
|
|
|
*/
|
|
|
|
|
|
toggleFullscreen() {
|
|
|
|
|
|
if (!this.allowFullscreen) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.isFullscreen) {
|
|
|
|
|
|
this.enterFullscreen();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.exitFullscreen();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 进入全屏
|
|
|
|
|
|
*/
|
|
|
|
|
|
enterFullscreen() {
|
|
|
|
|
|
const element = this.$refs.editorContainer;
|
|
|
|
|
|
if (!element) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const requestFullscreen =
|
|
|
|
|
|
element.requestFullscreen ||
|
|
|
|
|
|
element.webkitRequestFullscreen ||
|
|
|
|
|
|
element.mozRequestFullScreen ||
|
|
|
|
|
|
element.msRequestFullscreen;
|
|
|
|
|
|
|
|
|
|
|
|
if (requestFullscreen) {
|
|
|
|
|
|
requestFullscreen.call(element);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.$message?.warning?.('您的浏览器不支持全屏功能');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 退出全屏
|
|
|
|
|
|
*/
|
|
|
|
|
|
exitFullscreen() {
|
|
|
|
|
|
const exitFullscreen =
|
|
|
|
|
|
document.exitFullscreen ||
|
|
|
|
|
|
document.webkitExitFullscreen ||
|
|
|
|
|
|
document.mozCancelFullScreen ||
|
|
|
|
|
|
document.msExitFullscreen;
|
|
|
|
|
|
|
|
|
|
|
|
if (exitFullscreen) {
|
|
|
|
|
|
exitFullscreen.call(document);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置全屏监听
|
|
|
|
|
|
*/
|
|
|
|
|
|
setupFullscreenListener() {
|
|
|
|
|
|
const events = [
|
|
|
|
|
|
'fullscreenchange',
|
|
|
|
|
|
'webkitfullscreenchange',
|
|
|
|
|
|
'mozfullscreenchange',
|
|
|
|
|
|
'msfullscreenchange'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
events.forEach(event => {
|
|
|
|
|
|
document.addEventListener(event, this.handleFullscreenChange);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理全屏监听
|
|
|
|
|
|
*/
|
|
|
|
|
|
cleanupFullscreenListener() {
|
|
|
|
|
|
const events = [
|
|
|
|
|
|
'fullscreenchange',
|
|
|
|
|
|
'webkitfullscreenchange',
|
|
|
|
|
|
'mozfullscreenchange',
|
|
|
|
|
|
'msfullscreenchange'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
events.forEach(event => {
|
|
|
|
|
|
document.removeEventListener(event, this.handleFullscreenChange);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理全屏变化
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleFullscreenChange() {
|
|
|
|
|
|
this.isFullscreen = !!(
|
|
|
|
|
|
document.fullscreenElement ||
|
|
|
|
|
|
document.webkitFullscreenElement ||
|
|
|
|
|
|
document.mozFullScreenElement ||
|
|
|
|
|
|
document.msFullscreenElement
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查移动端
|
|
|
|
|
|
*/
|
|
|
|
|
|
checkMobile() {
|
|
|
|
|
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
|
|
|
|
this.isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
|
|
|
|
|
|
userAgent.toLowerCase()
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 销毁编辑器
|
|
|
|
|
|
*/
|
|
|
|
|
|
destroyEditor() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (this.editor && typeof this.editor.destroy === 'function') {
|
|
|
|
|
|
this.editor.destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('销毁编辑器时出错:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
const container = this.$refs.editorContainer;
|
|
|
|
|
|
if (container) {
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
this.editor = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理错误
|
|
|
|
|
|
* @param {String} message - 错误消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleError(message) {
|
|
|
|
|
|
this.error = true;
|
|
|
|
|
|
this.errorMessage = message || '未知错误';
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
this.$emit('error', message);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重试加载
|
|
|
|
|
|
*/
|
|
|
|
|
|
retry() {
|
|
|
|
|
|
if (this.loading) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.error = false;
|
|
|
|
|
|
this.errorMessage = '';
|
|
|
|
|
|
this.initViewer();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭查看器
|
|
|
|
|
|
*/
|
|
|
|
|
|
handleClose() {
|
|
|
|
|
|
if (this.closing) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.closing = true;
|
|
|
|
|
|
this.$emit('close');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理资源
|
|
|
|
|
|
*/
|
|
|
|
|
|
cleanup() {
|
|
|
|
|
|
this.destroyEditor();
|
|
|
|
|
|
this.cleanupFullscreenListener();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.onlyoffice-viewer {
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.viewer-toolbar {
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border-bottom: 1px solid #e0e0e0;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
min-height: 48px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left,
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-btn {
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
|
border-color: #bbb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.download {
|
|
|
|
|
|
background: #27ae60;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: #27ae60;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
|
background: #219a52;
|
|
|
|
|
|
border-color: #219a52;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.document-title {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
max-width: 300px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.viewer-loading,
|
|
|
|
|
|
.viewer-error {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
padding: 40px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-content,
|
|
|
|
|
|
.error-content {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-spinner {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border: 4px solid #f3f3f3;
|
|
|
|
|
|
border-top: 4px solid #3498db;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 1s linear infinite;
|
|
|
|
|
|
margin: 0 auto 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-icon {
|
|
|
|
|
|
font-size: 48px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-content h3 {
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
color: #e74c3c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-content p {
|
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-btn {
|
|
|
|
|
|
background: #3498db;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-btn:hover {
|
|
|
|
|
|
background: #2980b9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.editor-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
background: #f5f5f5;
|
2025-11-05 09:47:20 +08:00
|
|
|
|
min-height: 200px;
|
|
|
|
|
|
display: block !important;
|
|
|
|
|
|
/* 确保容器始终显示 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 确保 OnlyOffice iframe 正确显示 */
|
|
|
|
|
|
::v-deep iframe {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
2025-11-04 13:22:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.editor-container.fullscreen {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100vw;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mobile-tip {
|
|
|
|
|
|
background: #fff3cd;
|
|
|
|
|
|
border: 1px solid #ffeaa7;
|
|
|
|
|
|
color: #856404;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.viewer-toolbar {
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left,
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.document-title {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin: 4px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|