smart-bid-web/src/views/common/OnlyOfficeViewer.vue

483 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="onlyoffice-container">
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<div class="loading-spinner"></div>
<p>正在加载文档...</p>
</div>
<!-- 错误状态 -->
<div v-if="error" class="error-state">
<div class="error-icon">❌</div>
<h3>加载失败</h3>
<p>{{ error }}</p>
<button @click="retry" class="retry-btn">重试</button>
</div>
<!-- 编辑器容器(始终存在,通过样式控制显示) -->
<div v-show="!loading && !error" id="placeholder" class="editor-placeholder"></div>
</div>
</template>
<script>
import { getConfigAPI, generateCallbackTokenAPI } from '@/api/common/onlyOfficeViewer'
export default {
name: 'OnlyOfficeViewer',
props: {
// 可以添加 props 来动态配置
documentUrl: {
type: String,
default: 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/11/03/716d9f3d89434c56bc49296dbbccc226.docx'
},
documentTitle: {
type: String,
default: '716d9f3d89434c56bc49296dbbccc226.docx'
},
documentKey: {
type: String,
default: '1'
},
fileName: {
type: String,
default: 'technicalSolutionDatabase/2025/11/03/716d9f3d89434c56bc49296dbbccc226.docx'
},
mode: {
type: String,
default: 'view', // 'view' 或 'edit'
validator: (value) => ['view', 'edit'].includes(value)
},
type: {
type: String,
default: 'desktop', // 'desktop', 'mobile', 'embedded'
validator: (value) => ['desktop', 'mobile', 'embedded'].includes(value)
}
},
data() {
return {
docEditor: null,
onlyOfficeScriptLoaded: false,
editorConfig: null,
configReady: false,
loading: false,
error: null
};
},
async mounted() {
try {
this.loading = true;
this.error = null;
if(this.docEditor){
this.destroyEditor();
}
// 先加载配置
await this.getConfig();
// 再初始化 OnlyOffice成功后会自动设置 loading = false
await this.initOnlyOffice();
} catch (error) {
console.error('初始化失败:', error);
this.loading = false;
this.error = error.message || '初始化失败';
this.$emit('error', error);
}
},
beforeDestroy() {
this.destroyEditor();
},
methods: {
// 加载编辑器配置
async getConfig() {
try {
console.log('开始获取编辑器配置...', {
fileId: this.documentKey,
mode: this.mode,
type: this.type
});
const res = await getConfigAPI({
fileId: this.documentKey,
mode: this.mode,
type: this.type
});
console.log('获取配置响应:', res);
if (res.code !== 200) {
throw new Error(res.msg || '获取编辑器配置失败');
}
if (!res.data) {
throw new Error('配置数据为空');
}
// 验证必要的配置字段
if (!res.data.document || !res.data.document.url) {
throw new Error('配置中缺少文档URL');
}
// 设置配置,添加事件回调
this.editorConfig = {
...res.data,
events: {
onDocumentReady: () => this.onDocumentReady(),
onError: (error) => this.onEditorError(error),
onAppReady: () => this.onAppReady(),
}
};
this.configReady = true;
console.log('编辑器配置准备完成:', this.editorConfig);
} catch (error) {
console.error('获取编辑器配置失败:', error);
this.error = error.message || '获取编辑器配置失败';
throw error;
}
},
/**
* 初始化 OnlyOffice
*/
async initOnlyOffice() {
try {
// 确保配置已准备好
if (!this.configReady || !this.editorConfig) {
throw new Error('编辑器配置未准备好,请先加载配置');
}
// 加载 OnlyOffice 脚本
console.log('开始加载 OnlyOffice 脚本...');
await this.loadOnlyOfficeScript();
console.log('OnlyOffice 脚本加载完成');
// 等待 DOM 准备好
await this.$nextTick();
// 初始化编辑器
await this.initDocEditor();
} catch (error) {
console.error('初始化 OnlyOffice 失败:', error);
this.error = error.message || '初始化 OnlyOffice 失败';
this.$emit('error', error);
throw error;
}
},
/**
* 加载 OnlyOffice 脚本
*/
loadOnlyOfficeScript() {
return new Promise((resolve, reject) => {
// 如果已经加载,直接返回
if (window.DocsAPI) {
this.onlyOfficeScriptLoaded = true;
resolve();
return;
}
// 检查是否正在加载
const existingScript = document.querySelector('script[data-onlyoffice]');
if (existingScript) {
existingScript.addEventListener('load', () => {
this.onlyOfficeScriptLoaded = true;
resolve();
});
existingScript.addEventListener('error', reject);
return;
}
// 创建新的脚本元素
const script = document.createElement('script');
// onlyOfficeUrl 是 OnlyOffice 服务地址
const onlyOfficeUrl = process.env.VUE_APP_ONLYOFFICE_URL || process.env.VUE_APP_BASE_API || '';
if (!onlyOfficeUrl) {
reject(new Error('OnlyOffice 服务地址未配置,请检查环境变量 VUE_APP_ONLYOFFICE_URL'));
return;
}
const scriptUrl = `${onlyOfficeUrl}/web-apps/apps/api/documents/api.js`;
console.log('加载 OnlyOffice 脚本URL:', scriptUrl);
script.src = scriptUrl;
script.setAttribute('data-onlyoffice', 'true');
script.onload = () => {
this.onlyOfficeScriptLoaded = true;
// 等待 API 完全初始化
setTimeout(() => {
if (window.DocsAPI) {
resolve();
} else {
reject(new Error('OnlyOffice API 未正确加载'));
}
}, 100);
};
script.onerror = () => {
reject(new Error('加载 OnlyOffice 脚本失败'));
};
document.head.appendChild(script);
});
},
/**
* 初始化文档编辑器
*/
async initDocEditor() {
// 验证 DocsAPI
if (!window.DocsAPI || !window.DocsAPI.DocEditor) {
throw new Error('OnlyOffice DocsAPI 未正确加载,请检查脚本是否已加载');
}
// 验证配置
if (!this.editorConfig) {
throw new Error('编辑器配置不存在');
}
// 验证容器元素(容器可能被 v-show 隐藏,但 DOM 中应该存在)
await this.$nextTick();
let container = document.getElementById('placeholder');
// 如果容器不存在,等待一下再试
if (!container) {
console.warn('容器元素不存在,等待 DOM 渲染...');
await new Promise(resolve => setTimeout(resolve, 100));
await this.$nextTick();
container = document.getElementById('placeholder');
}
if (!container) {
throw new Error('找不到编辑器容器元素 #placeholder请确保容器已正确渲染');
}
// 即使容器被 v-show 隐藏,也要强制显示以便创建编辑器
container.style.display = 'block';
container.style.visibility = 'visible';
container.style.width = '100%';
container.style.height = '100%';
console.log('容器元素:', container);
console.log('容器尺寸:', {
width: container.offsetWidth,
height: container.offsetHeight,
rect: container.getBoundingClientRect()
});
// 确保容器有尺寸
if (container.offsetWidth === 0 || container.offsetHeight === 0) {
console.warn('容器尺寸为0等待尺寸调整...');
await new Promise(resolve => setTimeout(resolve, 300));
if (container.offsetWidth === 0 || container.offsetHeight === 0) {
console.warn('容器尺寸仍为0但继续尝试创建编辑器');
}
}
try {
console.log('创建 OnlyOffice 编辑器,配置:', this.editorConfig);
// 清空容器
container.innerHTML = '';
// 创建编辑器实例
this.docEditor = new window.DocsAPI.DocEditor('placeholder', this.editorConfig);
console.log('编辑器实例创建成功:', this.docEditor);
// 编辑器创建成功,隐藏加载状态
this.loading = false;
this.$emit('initialized', this.docEditor);
} catch (error) {
console.error('创建 OnlyOffice 编辑器失败:', error);
console.error('错误详情:', {
message: error.message,
stack: error.stack,
config: this.editorConfig
});
this.loading = false;
this.error = error.message || '创建编辑器失败';
this.$emit('error', error);
throw error;
}
},
/**
* 文档准备就绪回调
*/
onDocumentReady() {
console.log("文档准备好了");
this.$emit('document-ready');
},
/**
* 应用准备就绪回调
*/
onAppReady() {
console.log("OnlyOffice 应用准备就绪");
this.$emit('app-ready');
},
/**
* 编辑器错误回调
*/
onEditorError(error) {
console.error("OnlyOffice 错误:", error);
this.$emit('error', error);
},
/**
* 销毁编辑器
*/
destroyEditor() {
if (this.docEditor) {
try {
this.docEditor.destroyEditor()
} catch (error) {
console.warn('销毁编辑器时出错:', error);
}
}
this.docEditor = null;
},
/**
* 重新加载文档
*/
async reloadDocument(newConfig = {}) {
this.destroyEditor();
this.configReady = false;
this.error = null;
// 可以在这里更新配置
if (newConfig.documentUrl) {
this.documentUrl = newConfig.documentUrl;
}
if (newConfig.documentTitle) {
this.documentTitle = newConfig.documentTitle;
}
if (newConfig.mode) {
this.mode = newConfig.mode;
}
// 重新加载配置并初始化
try {
this.loading = true;
await this.getConfig();
await this.initDocEditor();
} catch (error) {
console.error('重新加载文档失败:', error);
this.error = error.message || '重新加载失败';
} finally {
this.loading = false;
}
},
/**
* 重试
*/
async retry() {
this.error = null;
this.loading = true;
try {
await this.getConfig();
await this.initOnlyOffice();
} catch (error) {
console.error('重试失败:', error);
this.error = error.message || '重试失败';
} finally {
this.loading = false;
}
}
}
};
</script>
<style scoped>
.onlyoffice-container {
width: 100%;
height: 90vh;
display: flex;
flex-direction: column;
position: relative;
}
.editor-placeholder {
width: 100%;
height: 100%;
min-height: 600px;
}
.loading-state,
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 400px;
text-align: center;
padding: 40px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error-state h3 {
margin: 0 0 8px 0;
color: #e74c3c;
font-size: 18px;
}
.error-state p {
margin: 0 0 16px 0;
color: #666;
font-size: 14px;
}
.retry-btn {
background: #3498db;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.retry-btn:hover {
background: #2980b9;
}
/* 确保 OnlyOffice iframe 正确显示 */
.editor-placeholder ::v-deep iframe {
width: 100% !important;
height: 100% !important;
border: none;
display: block;
}
</style>